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也可以随便排, 这也是为什么要从大到小算乘法原理的原因。