2022NOIPA层联测8
A. tothecrazyones
先考虑 \(a_i < x + y\) 的,那么两个人必然至多只有一个人能取同一堆石头,那么对于先手来说,取的越多越好,如果先手把能取的都取了,后手还能取,那么后手必胜,否则先手必胜
考虑一般情况,发现如果先手/后手希望保持某个状态,那么对方取完 $x $ 或 $ y$ , 自己可以跟着取 \(y\) 或 \(x\),相当于给 \(a_i\) 减去 \(x + y\) 不难发现此时答案不变,于是就可以对 \(x + y\) 取模,化归到第一种情况
code
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 500005;
ll a[maxn], n, x, y;
void solve(){
n = read(), x = read(), y = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i <= n; ++i)a[i] %= (x + y);
int fl = 1;
if(x <= y){
fl = 0;
for(int i = 1; i <= n; ++i)if(a[i] >= x){fl = 1; break;}
}else{
fl = 0;
for(int i = 1; i <= n; ++i)if(a[i] >= x){fl = 1; a[i] -= x;}
if(fl)for(int i = 1; i <= n; ++i)if(a[i] >= y){fl = 0; break;}
}
printf("%d\n",fl);
}
int main(){
int t = read();
for(int i = 1; i <= t; ++i)solve();
return 0;
}
B. vmefifty (CF1699D)
这考试原题我没发现,打了一个比原来复杂不少的代码。。。
\(f_i\) 表示考虑前 \(i\) 个,删到只剩 \(a_i\) 同色的,最多剩下多少
于是 \(f_i = max(f_j + 1 - cost[j + 1, i - 1])\)
其中 \(j\) 为所有 \(j < i, a_j == a_i\) \(cost\)为需要多少颜色为 \(a_i\) 的消去区间内的
然后那就是区间出现主元素时为 \(e + e - len\) 其他时候为 \(0\), 使用摩尔投票+桶预处理一下
注意区间长度为奇数时 \(cost\) 至少为 \(1\)
code
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<cassert>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 2055;
int f[maxn], cnt[maxn][maxn], mx[maxn][maxn];
int t, a[maxn], n, b[maxn];
vector<int>pos[maxn];
void solve(){
n = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i <= n; ++i)b[i] = a[i];
sort(b + 1, b + n + 1);
int up = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; ++i)a[i] = lower_bound(b + 1, b + up + 1, a[i]) - b;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= up; ++j)cnt[i][j] = cnt[i - 1][j];
++cnt[i][a[i]];
}
for(int l = 1; l <= n; ++l){
int las = -1, count = 0;
for(int r = l; r <= n; ++r){
if(count == 0)las = a[r], count = 1;
else if(las != a[r])--count;
else ++count;
mx[l][r] = max(0, (cnt[r][las] - cnt[l - 1][las]) * 2 - (r - l + 1));
if((r - l + 1) & 1){
mx[l][r] = max(1, mx[l][r]);
}
}
}
for(int i = 1; i <= up; ++i)pos[i].push_back(0);
for(int i = 1; i <= n; ++i)pos[a[i]].push_back(i);
for(int i = 1; i <= up; ++i)pos[i].push_back(n + 1);
for(int i = 1; i <= n + 1; ++i)f[i] = -maxn;
f[0] = 0; int ans = 0;
for(int i = 1; i <= up; ++i){
for(int now : pos[i]){
for(int las : pos[i]){
if(las == now)break;
f[now] = max(f[now], f[las] - mx[las + 1][now - 1] + 1);
}
}
}
for(int i = 1; i <= n; ++i)pos[i].clear();
printf("%d\n",n - f[n + 1] + 1);
}
int main(){
t = read();
for(int i = 1; i <= t; ++i)solve();
return 0;
}
C. wishusgoodluck(CF662C)
学习了 \(fwt\)
题解讲的很好。懒了
code
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxl = 100005;
const int maxn = 1048579;
const int mod = 15620947;
const int inv2 = 7810474;
int cnt[1148576], len;
void fwt_xor(int f[], int opt){
opt = opt == -1 ? inv2 : 1;
for(int l = 2, k = 1; l <= len; l <<= 1, k <<= 1)
for(int i = 0; i < len; i += l)
for(int j = 0; j < k; ++j){
int x = f[i + j], y = f[i + j + k];
f[i + j] = 1ll * opt * (x + y) % mod;
f[i + j + k] = 1ll * opt * (x - y + mod) % mod;
}
}
int n, m;
char mp[25][maxl];
int a[maxl];
int f[maxn], g[maxn];
int main(){
scanf("%d%d",&n, &m);
for(int i = 1; i <= n; ++i)scanf("%s",mp[i] + 1);
for(int i = 1; i <= m; ++i){
for(int j = 1; j <= n; ++j)a[i] |= ((mp[j][i] == '1') << (j - 1));
}
len = 1 << n;
for(int i = 1; i < len; ++i)cnt[i] = cnt[i >> 1] + (i & 1);
for(int i = 0; i < len; ++i)g[i] = min(cnt[i], n - cnt[i]);
for(int i = 1; i <= m; ++i)++f[a[i]];
fwt_xor(f, 1); fwt_xor(g, 1);
for(int i = 0; i < len; ++i)f[i] = 1ll * f[i] * g[i] % mod;
fwt_xor(f, -1);
int ans = 0x3f3f3f3f;
for(int i = 0; i < len; ++i)ans = min(ans, f[i]);
printf("%d\n",ans);
return 0;
}
D. xiongaktenioi(CF1034C)
如果把树砍成 \(k\) 份,那么每块大小为 \(s/k\) ,而且划分方案唯一
断开了某个点与其父亲,当且仅当 \(s_i \equiv 0 \mod s / k]\)
当这样的点存在 \(k\) 个时,把树划分成 \(k\) 份就是合法的
设 $f_k =\sum_{i = 1}^n [s_i \equiv 0 \mod s / k] $
考虑快速求出 \(f_k\) , 反过来考虑 \(s_i\) 的贡献
令 \(t = s / k\)
那么 \(k = s / t\)
\(s_i \equiv 0 \mod t\) 等价 \(s_i = at\)
上式均为整数,于是 \(t | s_i\) \(t |s\),那么 \(t | gcd(s, s_i)\)
也就是 \((s/ k) | gcd(s, s_i)\) 即 \((s/ gcd(s, s_i) )| k\)
我们只需要 \(k <= n\) 的贡献,于是开桶枚举倍数即可快速求解 \(f_k\)
然后考虑求方案,令 \(g_i = [f_i == i]\)
\(g_i\) 表示划分成 \(i\) 块的方案数
那么能划分成 \(i\) 块,并且能够划分成 \(ai\) 块 ,就有了先划分 \(i\) 块,再划分 \(ai\) 块的方案
于是从小到大去他的 合法倍数 处贡献
code
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 1000005;
const int mod = 1e9 + 7;
int n, a[maxn], fa[maxn];
ll si[maxn];
int f[maxn], cnt[maxn], g[maxn];
int main(){
n = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 2; i <= n; ++i)fa[i] = read();
for(int i = n; i >= 1; --i){si[i] += a[i]; si[fa[i]] += si[i];}
for(int i = 1; i <= n; ++i){
ll now = si[1] / __gcd(si[i], si[1]);
if(now <= n)++cnt[now];
}
for(int i = 1; i <= n; ++i)if(cnt[i])
for(int j = i; j <= n; j += i)
f[j] += cnt[i];
for(int i = 1; i <= n; ++i)f[i] = f[i] == i;
for(int i = 1; i <= n; ++i)g[i] = f[i];
int ans = 1;
for(int i = 2; i <= n; ++i){
ans = (ans + g[i]) % mod;
for(int j = i + i; j <= n; j += i)if(f[j])g[j] = (g[j] + g[i]) % mod;
}
printf("%d\n",ans);
return 0;
}