C - A Cookie for You【贪心】
题意: if the guest of the first type: if v>c the guest selects a vanilla cookie. Otherwise, the guest selects a chocolate cookie.
if the guest of the second type: if v>c the guest selects a chocolate cookie. Otherwise, the guest selects a vanilla cookie.
思路: 可以发现,第一种类型的人只要有蛋糕就一定能吃(总是吃数目多的), 而第二种人只吃少的。所以只要两种蛋糕中较少的那个够第二种人吃就行。另外总的蛋糕要足够。

void solve(){
    cin >> a >> b >> n >> m;
    if(a + b < n + m)    dead();
    else{
        if(min(a, b) < m)    dead();
        else    alive();
    }
} 

D. Grid-00100 【思维】
题意:n * n的01矩阵,可以填k个1.问这个式子的最小值:(R,C分别表示某一行、列中1的个数)
f(A)=(max(R)−min(R))2+(max(C)−min(C))2
思路:可以发现,如果k % n == 0,答案就是0【可以凑成每行、每列都是k / n个, 例如n = 4, k = 12,每一行的填充情况可以是: 123 124 134 234】,否则答案就是2【maxr-minr == 1, maxc - minc = 1,画图即可】。

void solve(){
    cin >> n >> k;
    if(k % n == 0)    puts("0");
    else    puts("2");
    int num = k / n;
    for(int i = 1; i <= n; ++ i)
        for(int j = 1; j <= num; ++ j){
            int now = (i + j - 1) % n;
            if(!now)    now = n;
            mp[i][now] = 1;
        }
    if(k % n){
        int remain = k % n;
        for(int i = 1; i <= n && remain; ++ i){
            int last = (i + num) % n;
            if(!last)    last = n;
            mp[i][last] = 1;
            remain --;
        }
    }
    for(int i = 1; i <= n; ++ i){
        for(int j = 1; j <= n; ++ j)
            printf("%d", mp[i][j]), mp[i][j] = 0;
        cout << endl;
    }
}

E1. Asterism (Easy Version) && E2. Asterism (Hard Version)【二分+贪心+乘法原理, 好题】
题意:“有n个敌人, 每个人有ai朵花。 小女孩初始有x朵花, 她可以随意确定敌人的顺序(有n!种), 如果她的花>=a[i], 可以获得1朵花,否则继续。小女孩想赢得所有的敌人,她可以有多少种顺序排列敌人?” ——我们记函数f(x)为小女孩有x朵初始花时,可以获胜的敌人顺序总数。然后给出一个质数p, 询问有多少个x, 他们分别是多少, 可以使得f(x) % p != 0?
tips:easy&hard只是范围问题, easy中n, a[i] <= 2000, hard中n <= 1e5, a[i] <= 1e9.
思路:先预处理出n的阶乘 % p.
证明二分的正确性。如果一个x算出的f(x) % p == 0, 那么所有比他大的x均不可能。【因为x更大了,当前对于x合适的排列方式对于更大的x一定更成立, 而且还有更多的情况,f(x) % p == 0, 那f(x) * 多出来的更多的排列数 % p 也 == 0】
先二分求出下界(低于这个值得x无法保证赢得所有敌人), 再二分求出上界(高于这个值得x会使得f(x) % p == 0).
注意如何写第二个二分的check: 先从高位开始,当前下标是i, 表示前面1-i的位置还没被占用; 如果a[i] <= mid, 表示这i个位置随便排均可(而且后面的a[i]都比mid小), ans *= fact[i];
否则,a[i]最早被放在a[i] - mid + 1这个位置上, 共有i - (a[i] - mid + 1) + 1种方式.

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 200010;

ll fact[N], a[N];
int n, t, p;

bool check1(int minn){
    for(int i = 1; i <= n; ++ i)
        if(minn >= a[i])    minn ++;
        else return 0;
    return 1;    
} 

//bool check(int mid){
//    int pos = upper_bound(a + 1, a + 1 + n, mid) - a - 1;
//    vector<int> v;
//    for(int i = pos + 1; i <= n; ++ i)
//         if(mp[a[i]] > 1)    v.push_back(fact[mp[a[i]]]), mp[a[i]] = 0;
//    //printf("mid = %d pos = %d\n", mid, pos);
//    int ans = fact[pos];
//    for(auto u : v)    ans = (ll)ans * u % p;
//    return ans;
//}

bool check(int mid){
    int ans = 1;
    for(int i = n; i >= 1; -- i){
        if(a[i] > mid)    ans = (ll)ans * fact[i - a[i] + mid] % p;
        else    ans = (ll)ans * fact[i] % p;
    }
    return ans;
}

void solve(){
    cin >> n >> p;
    fact[0] = fact[1] = 1;
    for(int i = 2; i <= n; ++ i)    fact[i] = fact[i - 1] * i % p;
    for(int i = 1; i <= n; ++ i)    scanf("%lld", &a[i]);
    sort(a + 1, a + 1 + n);
    int minn = a[1], maxx = a[n];
    while(minn < maxx){        //先二分求最小值或直接暴力(Easy Verson)求最小值 
        int mid = (ll)minn + maxx >> 1;
        if(check1(mid))        maxx = mid;
        else    minn = mid + 1;
    }
    if(!check(maxx)){
        puts("0");
        return ;
    }
    int l = maxx, r = 1e9;
    while(l < r){
        int mid = l + r + 1 >> 1;
        if(check(mid))    l = mid;
        else    r = mid - 1;
        //printf("l = %d r = %d mid = %d\n", l, r, mid);
    } 
    cout << l - minn + 1 << endl;
    for(int i = minn; i <= l; ++ i)    printf("%d ", i);
}

int main(){
//    cin >> t;
//    while(t --)
    solve();
    return 0;
}

Tipssss:注意找上界的二分不可以这么贪心(代码中的注释部分):

bool check(int mid){
    int pos = upper_bound(a + 1, a + 1 + n, mid) - a - 1;
    vector<int> v;
    for(int i = pos + 1; i <= n; ++ i)
         if(mp[a[i]] > 1)    v.push_back(fact[mp[a[i]]]), mp[a[i]] = 0;
    //printf("mid = %d pos = %d\n", mid, pos);
    int ans = fact[pos];
    for(auto u : v)    ans = (ll)ans * u % p;
    return ans;
}

这个代码的意思是: 小于mid的数随便排, 大于Mid的数如果有多个,这些相同的数之间可以随便排。
错误原因:大于Mid的数并不是只有一种排列方式, 如1 2 3 4 5, 如果mid = 3, 完全可以排列成1 2 3 5 4, 因为mid在经过1 2 3之后已经变成6了,4 5也可以随便排, 这也是为什么要从大到小算乘法原理的原因。