AGC 045 部分简要题解
吹水
考场只 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();}