吹水
考場只 A 了一道題屬實沒救了。
不懂啊,怎么感覺 B 比 F 難...
A - Xor Battle
倒着考慮,遇到一個 \(1\) 的輪的時候,如果當前數字不在之后所有數字的線性基中,那么 \(1\) 顯然有必勝策略。否則是否異或不會影響到之后是否勝利。
維護線性基即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1010, M = 64;
ll a[N];int n;char s[N];ll b[N];
ll insert(ll x){
for(int i=60;~i;i--)if(1&(x>>i)){
if(!b[i]){b[i]=x;break;}
else x^=b[i];
}
return x;
}
void Main()
{
for(int i=0;i<M;i++)b[i]=0;
cin >> n;for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
scanf("%s",s+1);
bool Ans=0;
for(int i=n;i;i--){
if(s[i]=='0'){
insert(a[i]);
}else{
ll d=insert(a[i]);
// cerr << d << "??" << endl;
Ans|=d!=0;
}
}
printf("%d\n",(int)Ans);
}
int main(){int T;cin >> T;while(T--)Main();}
B - 01 Unbalanced
第一步顯然是讓 \(0\) 變成 \(-1\),\(1\) 變成 \(1\),現在就變成前綴和的最大最小值只差。
一個好想的做法:(考場根本沒往這方面想)二分答案,硬點 \(s_0=k\),最后要求 \(0\le s_i \le \text{mid}\)。硬點實際是不需要的,可以直接弄到一起做轉移(\(s_i=k\) 是否可行)。
另外一個做法:考慮 \(max\) 確定的時候有容易證明的貪心:先讓問號變成 \(-1\),從左到右依次考慮能否把當前變成 \(1\),可以則變。冷靜分析可以發現的是我們令 \(max=t\) 的時候得到的 \(min\) 是 \(f(t)\),實際上是有:\(f(t) \le f(t+2)+2\) 的。證明大概是考慮一個后綴如果被多增加了兩次,那么實際上一定能在 \(f(t)\) 中多增加一次的。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+5;
int n;
char s[N];
int g[N];
int v[N], h[N];
int main()
{
scanf("%s",s+1);n=strlen(s+1);
int mx=0,ss=0;
for(int i=1;i<=n;i++){
if(s[i]=='1')ss++;
else ss--;
v[i]=h[i]=ss;
mx=max(mx,ss);
}
int mn=0;
for(int i=n-1;i;i--)v[i]=max(v[i],v[i+1]);
ss=0;
for(int i=1;i<=n;i++){
if(s[i]=='1')ss++;
else if(s[i]=='0')ss--;
else if(s[i]=='?'){
if(ss+v[i]+2-h[i-1]<=mx)ss++;
else ss--;
}
mn=min(mn,ss);
}
int ans=mx-mn;
mn=ss=0;
++mx;
for(int i=1;i<=n;i++){
if(s[i]=='1')ss++;
else if(s[i]=='0')ss--;
else if(s[i]=='?'){
if(ss+v[i]+2-h[i-1]<=mx)ss++;
else ss--;
}
mn=min(mn,ss);
}
ans=min(ans,mx-mn);
printf("%d\n",ans);
}
C - Range Set
考慮倒推,發現只要倒推到某一步驟兩個都能操作那一定能全部變成 \(0\) 了。
優先考慮的顯然是 \(A,B\) 中較小的操作,操作完這樣的區間之后如果較大的那個能夠操作,那么就能一直操作下去了。
於是就是一個簡單的 dp。
#include<bits/stdc++.h>
using namespace std;
const int N = 5020;
const int mod=1e9+7;
typedef long long ll;
inline int add(int a,int b){a+=b;return a>=mod?a-mod:a;}
inline int sub(int a,int b){a-=b;return a<0?a+mod:a;}
inline int mul(int a,int b){return 1ll*a*b%mod;}
inline int qpow(int a,int b){int ret=1;for(;b;b>>=1,a=mul(a,a))if(b&1)ret=mul(ret,a);return ret;}
int g[N][2];
int f[N][2];
int ans=0, n, a, b;
int main()
{
cin >> n >> a >> b;
if(a>b)swap(a,b);
if(b>1){
f[0][0]=f[0][1]=1;
for(int i=1;i<=n;i++){
for(int k=0;k<2;k++){
for(int j=1;j<=i;j++){
if(k==0&&j<a)continue;
f[i][k]=add(f[i][k],f[i-j][k^1]);
}
}
}
f[0][0]=0;
for(int i=1;i<n;i++){
if(i<a)g[i][0]=add(g[i][0],1);
for(int j=1;j<min(i,a);j++){
g[i][0]=add(g[i][0], g[i-j][1]);
}
if(i<b)g[i][1]=add(g[i][1], add(f[i-1][0],f[i-1][1]));
for(int j=1;j<min(i,b);j++){
int w=j==1?1:add(f[j-2][0],f[j-2][1]);
g[i][1]=add(g[i][1],1ll*g[i-j][0]*w%mod);
}
if(n-i<b)ans=add(ans, mul(g[i][0], add(f[n-i-1][0],f[n-i-1][1])));
if(n-i<a)ans=add(ans, g[i][1]);
}
}
cout << sub(qpow(2,n), ans) << endl;
}
D - Lamps and Buttons
策略是每次找一個最小的點亮的且不知道環的把這個環弄出來,直到所有點都點亮。唯一的問題是有環沒有被點亮,或者有個自環。
現在枚舉最后一個自環的位置 \(c\),這個點硬點成為了自環,之后分成了 \(c-1\) , \(a-c\), \(n-a\) 的三段,(以下用 \(x,z,y\) 分別表示):
現在只需要求: 前 \(x\) 個無自環,之后 \(y\) 點所在的環有至少一個是前 \(x\) 個點,之后\(z\) 個隨意。
容斥掉無自環的條件,變成:前 \(x\) 個隨意,之后 \(y\) 個點所在的環有至少一個是前 \(x\) 個點,之后 \(z\) 個隨意。
這個不難推出來是:\(\frac{(x+y+z)!}{x+y}*x\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
inline int add(int a,int b){a+=b;return a>=mod?a-mod:a;}
inline int sub(int a,int b){a-=b;return a<0?a+mod:a;}
inline int mul(int a,int b){return 1ll*a*b%mod;}
inline int qpow(int a,int b){int ret=1;for(;b;b>>=1,a=mul(a,a))if(b&1)ret=mul(ret,a);return ret;}
/* math */
const int N = 1e7+5;
const int M = 5010;
int fac[N], ifac[N], inv[N];
inline void init(int n=1e7){
fac[0]=ifac[0]=1;for(int i=1;i<=n;i++)fac[i]=mul(fac[i-1],i);
ifac[n]=qpow(fac[n],mod-2);for(int i=n-1;i;--i)ifac[i]=mul(ifac[i+1],i+1);
inv[1]=1;for(int i=2;i<=n;i++)inv[i]=mul(mod-mod/i,inv[mod%i]);
}
int g[M];
int n,a;
inline int binom(int a,int b){return mul(fac[a],mul(ifac[b],ifac[a-b]));}
inline int solve(int a,int b,int c){return mul(fac[a+b+c],mul(a,inv[a+b]));}
int ans=0;
int main()
{
init();
cin >> n >> a;
for(int i=1;i<=a;i++){
for(int j=1;j<=i;j++){
int w=binom(i,j);if((i-j)&1)w=sub(0,w);
int qwq=solve(j,n-a,max(0,a-i-1));
ans=add(ans, mul(w,qwq));
}
}
cout << ans << endl;
}
E - Fragile Balls
咕咕咕
F - Division into Multiples
這個是可以搞成 \(a,b,c\) 兩兩互質的關系的。具體可以看代碼。
現在考慮最優的一對 \(p,q\) 滿足 \(p*a+q*b\equiv 0 \bmod c\)。顯然不能有 \(p,q\) 都更小的一段。
\(p\) 增加 \(1\) 之后,\(q\) 就需要減少 \(D = a/b \bmod c\) (取模意義下)。那么相當於是從 \((0,c)\) 一直走 \((1,-D)\),然后去掉左下角有點的坐標。
很容易觀察到最后得到的點構成一個凹殼,且卸率最多由 \(\log\) 個等差數列構成。
得到這個等差數列的方法有很多種,比如直接考慮將 \(c\) 分成若干鏈,這樣是可以遞歸下去的。
題解做法是考慮一個 \(c*D\) 的格子。每次向右上角走,坐標每次分別對 \(c,D\) 取模。把所有走到橫坐標軸上的前綴 max 取出來。這樣與原問題是一一對應的。對於這樣一個問題可以類似擴歐那樣遞歸下去,也可以輕松證明其復雜度。
upd: 這個做法的正確性仍然需要依靠前綴min都在凸殼上,然而可以發現的是當斜率增大的時候,向量的橫坐標也在增大,所以容易證明不存在以上問題。
跑多步的時候相當於對這些向量做 \(min\) 卷積,這個時候因為是凹殼,這是一個閔可夫斯基和。所以剩下的部分二分答案可以輕松做到 \(log^2V\) 。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a,b,x,y,c;
inline int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
inline void exgcd(int a,int b,int &x,int &y){
if(b)exgcd(b,a%b,y,x),y-=a/b*x;
else x=1,y=0;
}
inline int inv(int x,int p){//gcd(a,b)=0;
int a,b;exgcd(x,p,a,b);
if(a<0)a+=p;
return a;
}
vector<int> s, t;
inline int gety(int x,int step){
if(x==0)return c;
return (c-1ll*x*step%c)%c;
}
inline int lowdiv(int x,int y){
return x/y-(x%y&&(x^y)<0);
}
void Main(){
int ans=0;
cin >> a >> x >> b >> y >> c;
int g=gcd(a,b);a/=g,b/=g;
c/=gcd(c,g);
for(int _=1;_<=2;_++){
g=gcd(a,c);
c/=g,a/=g;int h=gcd(b,g);
b/=h,y/=g/h;
swap(a,b),swap(x,y);
}
if(c==1){printf("%lld\n",x+y);return ;}
int step=1ll*inv(b,c)*a%c;
vector<int> pos, cnt;
int z=inv(step, c), h=step, w=c, cur=0;
while(w){
int p=w/h;
pos.push_back(1ll*cur*z%c);
cnt.push_back(p);
cur+=p*h;
w%=h;
if(w==0)break;
h%=w;
if(h==0)h=w;
}
pos.push_back(c);
for(size_t i=0;i<cnt.size();i++){
int lx=pos[i], ly=gety(lx, step);
int rx=pos[i+1], ry=gety(rx, step);
int dx=(rx-lx)/cnt[i];
int dy=(ly-ry)/cnt[i];
int lw=0,up=x+y+1;
while(up-lw>1){
int mid=(lw+up)>>1;
int p=lowdiv(x-lx*mid,dx);
int q=lowdiv(y-ry*mid,dy);
if(p>=0&&q>=0&&p+q>=cnt[i]*mid)lw=mid;
else up=mid;
}
ans=max(ans, lw);
}
printf("%lld\n",ans);
}
#undef int
int main(){int T;cin >> T;while(T--)Main();}