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

京公网安备 11010502036488号