A - Xor Battle
可以发现,从后往前扫,遇到一个 \(1\) 找后面是否有若干个 \(0\) 的位置的 \(a_i\) 与当前位置的异或和相等,用线性基维护一下就好了。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=205;
int T;
int n;
long long a[N];
char s[N];
struct Basis
{
static const int M=60;
long long d[N];
void clear()
{
memset(d,0,sizeof(d));
return;
}
void insert(long long t)
{
for(int i=M;i>=0;i--)
if(t&(1LL<<i))
{
if(d[i]) t^=d[i];
else
{
for(int j=0;j<i;j++)
if(t&(1LL<<j)) t^=d[j];
for(int j=i+1;j<=M;j++)
if(d[j]&(1LL<<i)) d[j]^=t;
d[i]=t;
break;
}
}
return;
}
bool find(long long t)
{
for(int i=M;i>=0;i--)
if(t&(1LL<<i))
{
if(d[i]) t^=d[i];
else return false;
}
return true;
}
}lb;
void solve()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
scanf("%s",s+1);
lb.clear();
for(int i=n;i>=1;i--)
if(s[i]=='1')
{
if(!lb.find(a[i]))
{
printf("1\n");
return;
}
}
else if(s[i]=='0') lb.insert(a[i]);
printf("0\n");
return;
}
int main()
{
scanf("%d",&T);
while(T--)
solve();
return 0;
}
B - 01 Unbalanced
不妨先将所有的 ?
填成 1
,最后再调整。
把 1
看成 \(+1\),0
看成 \(-1\),做一个前缀和 \(s\),问题就变成了求 \(s\) 的最大值减最小值的值最小。
考虑枚举 \(s\) 的最小值 \(\min\),问题就变成了要使得 \(\max\) 最小。考虑从前往后决定每个位置是否将 ?
改成 0
,对 \(s\) 产生的影响即为后缀 \(-2\),可以证明这是最优的。
令 \(m=\min\\{s_i\\}\),可以发现,当 \(\min=m-2\) 时,不可能比 \(\min=m\) 时优,故我们只需要求 \(\min=m,m-1\) 的情况就好了。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1000005;
const int INF=1061109567;
int n;
char s[N];
int a[N];
int sum[N];
int suf[N];
int calc(int Min)
{
static int b[N];
int add=0;
for(int i=1;i<=n;i++)
{
if(s[i]=='?'&&suf[i]-add-2>=Min) add+=2;
b[i]=sum[i]-add;
}
int Max=max(*max_element(b+1,b+n+1),0);
return Max-Min;
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++)
if(s[i]=='1'||s[i]=='?') a[i]=1;
else if(s[i]=='0') a[i]=-1;
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+a[i];
suf[n+1]=INF;
for(int i=n;i>=1;i--)
suf[i]=min(suf[i+1],sum[i]);
int m=min(*min_element(sum+1,sum+n+1),0);
printf("%d",min(calc(m),calc(m-1)));
return 0;
}
C - Range Set
不妨令 \(A\leq B\),这两种情况是等价的,因为我们可以开始将整个序列染成 \(1\)。
考虑一个最后的字符串是否能被达到,可以将所有的长度 \(\ge A\) 的 \(0\) 段染成 \(1\),如果串内是否有长度 \(\ge B\) 的 \(1\) 段的话肯定是合法的,因为我们可以每次拓展一个 \(0\),否则是不合法的。
合法的方案很难算,不妨计算不合法的方案。
令 \(f_{i,0/1}\) 表示长度为 \(i\),以 \(0/1\) 结尾,段内没有长度 \(< A\) 的 \(0\) 的方案数,枚举上一个段的位置转移。
令 \(dp_{i,0/1}\) 表示长度为 \(i\),以 \(0/1\) 结尾,段内没有长度 \(< A\) 的 \(0\) 和长度 \(< B\) 的 \(1\) 的方案数,枚举上一个段的位置转移。
注意最后的长度 \(< B\) 的段可以 \(0\) 结尾。
代码:
#include<iostream>
#include<cstdio>
using namespace std;
const int N=5005;
const int MOD=1000000007;
int n,a,b;
long long f[N][2];
long long g[N];
long long dp[N][2];
long long ksm(long long a,long long b)
{
long long res=1;
while(b)
{
if(b&1) res=res*a%MOD;
a=a*a%MOD,b>>=1;
}
return res;
}
int main()
{
scanf("%d%d%d",&n,&a,&b);
if(a>b) swap(a,b);
if(b==1)
{
printf("%lld",ksm(2,n));
return 0;
}
f[0][0]=f[0][1]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<i;j++)
f[i][1]=(f[i][1]+f[j][0])%MOD;
for(int j=0;j+a<=i;j++)
f[i][0]=(f[i][0]+f[j][1])%MOD;
}
g[0]=1;
for(int i=1;i<=n;i++)
g[i]=(f[i][0]+f[i][1])%MOD;
long long ans=0;
for(int i=1;i<n;i++)
{
if(i<a) dp[i][0]=(dp[i][0]+1)%MOD;
for(int j=max(i-a+1,1);j<i;j++)
dp[i][0]=(dp[i][0]+dp[j][1])%MOD;
if(i<b) dp[i][1]=(dp[i][1]+g[i-1])%MOD;
for(int j=max(i-b+1,1);j<i;j++)
dp[i][1]=(dp[i][1]+dp[j][0]*(i-j==1?1:g[i-j-2])%MOD)%MOD;
if(i+b-1>=n) ans=(ans+dp[i][0]*g[n-i-1]%MOD)%MOD;
if(i+a-1>=n) ans=(ans+dp[i][1])%MOD;
}
printf("%lld",(ksm(2,n)-ans+MOD)%MOD);
return 0;
}
D - Lamps and Buttons
可以发现,不合法的情况为环中一个点都没有被点亮,或者有个自环他去点了。
考虑枚举第一个 \(p_i=i\) 且 \(i\leq A\) 的位置,如果不存在则 \(i=A+1\)。可以发现对于所有 \(A< j\le n\),\(j\) 所在的环至少存在一个小于 \(i\) 的点。
对于 \(i\),我们需要满足 \(j< i\) 的所有位置 \(p_j\neq j\),这个可以容斥解决。可以将点分成几类:
- \(p_i = i\) 的点,可以将这些点去掉。
- \(1\leq i< t\) 的点,记为 \(a\) 个。
- \(A< i\le n\) 的点。记为 \(b\) 个。每个点所在环中都要至少存在一个小于 \(i\) 的点。
- \(t< i\le A\) 的点。记为 \(c\) 个。
每次考虑依次插入每种点,计算方案数,最后乘一个容斥系数就好了。
代码:
#include<iostream>
#include<cstdio>
using namespace std;
const int N=10000005,M=5005;
const int MOD=1000000007;
int n,m;
long long ksm(long long a,long long b)
{
long long res=1;
while(b)
{
if(b&1) res=res*a%MOD;
a=a*a%MOD,b>>=1;
}
return res;
}
long long fac[N],inv[N];
void init(int n=10000000)
{
fac[0]=1;
for(int i=1;i<=n;i++)
fac[i]=fac[i-1]*i%MOD;
inv[n]=ksm(fac[n],MOD-2);
for(int i=n;i>=1;i--)
inv[i-1]=inv[i]*i%MOD;
return;
}
long long C(int n,int m)
{
if(m>n) return 0;
else return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int main()
{
scanf("%d%d",&n,&m);
init();
long long ans=0;
for(int i=1;i<=m+1;i++)
for(int j=0;j<i;j++)
{
int a=i-1-j,b=n-m,c=max(m-i,0);
long long res=C(i-1,j);
res=(res*fac[a])%MOD;
res=(res*fac[a+b-1]%MOD*inv[a-1]%MOD)%MOD;
res=(res*fac[a+b+c]%MOD*inv[a+b]%MOD)%MOD;
if(j&1) ans=(ans-res+MOD)%MOD;
else ans=(ans+res)%MOD;
}
printf("%lld",ans);
return 0;
}
E - Fragile Balls
考虑 \(C_i=1\) 的情况,将原图上连一条 \(A_i\to B_i\) 的边,可以分成几类:
- 只包含一个自环;
- 只包含一个长度大于等于 \(2\) 的简单环;
- 联通块的边数大于点数。
当存在第 2 种联通块的话,答案就是 \(-1\);否则答案就是 \(P\),\(P\) 表示图中的 \(A_i\neq B_i\) 的数量。
考虑 \(C_i\ge 1\) 的情况,我们可以调整使得有解。将一个当前联通块外面的点放进来,然后再处理。可以发现,令 \(cnt\) 表示第二种联通块的数量,我们至少需要额外多 \(cnt\) 步。
考虑从外面移入一个球 \(x\),分几种情况讨论当前点多出的步数:
- \(x\) 在第 1 种联通块内,对联通块数贡献为 \(-(C_x-2)\),需要多花费两步。
- \(x\) 在第 2 种联通块内,对联通块数贡献为 \(-(C_x-1)\),不需要多花费步数。
- \(x\) 在第 3 种联通块内,且 \(A_x=B_x\),对联通块数贡献为 \(-(C_x-1)\),需要多花费一步。
- \(x\) 在第 3 种联通块内,且 \(A_x\neq B_x\),对联通块数贡献为 \(-(C_x-1)\),不需要多花费步数。
对于 1 和 2,我们需要一个 3 或者 4 先移动进去,然后再操作。显然不需要多花费步数的都可以取。
接下来就只有 1 和 3 了,考虑枚举 1 的个数,然后 two-pointer 维护就好了。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N=1000005;
const long long INF=4557430888798830399;
int n,m;
int a[N],b[N],c[N];
int fa[N];
int getf(int v)
{
if(v==fa[v]) return v;
fa[v]=getf(fa[v]);
return fa[v];
}
void merge(int u,int v)
{
int f1=getf(u),f2=getf(v);
if(f1!=f2) fa[f2]=f1;
return;
}
int in[N],siz[N];
bool circle[N];
vector<int>pos[4];
long long s2[N],s0[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
fa[i]=i;
long long ans=0;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a[i],&b[i],&c[i]);
if(a[i]!=b[i]) ans++;
merge(a[i],b[i]);
in[a[i]]++;
}
memset(circle,true,sizeof(circle));
for(int i=1;i<=n;i++)
{
siz[getf(i)]++;
if(in[i]>1) circle[getf(i)]=false;
}
int cnt=0;
for(int i=1;i<=n;i++)
if(getf(i)==i)
{
if(circle[i]&&siz[i]>1) cnt++;
}
if(cnt==0)
{
printf("%lld",ans);
return 0;
}
ans+=cnt;
for(int i=1;i<=m;i++)
if(c[i]>=2)
{
int u=getf(a[i]);
if(circle[u])
{
if(siz[u]==1) pos[0].push_back(c[i]-2);
else if(siz[u]>1) pos[1].push_back(c[i]-1);
}
else
{
if(a[i]==b[i]) pos[2].push_back(c[i]-1);
else pos[3].push_back(c[i]-1);
}
}
for(int p=0;p<=3;p++)
sort(pos[p].begin(),pos[p].end(),greater<int>());
long long sum=0;
for(int p=0;p<=3;p++)
for(int c:pos[p])
sum+=c;
if(sum<cnt)
{
printf("-1");
return 0;
}
if(pos[2].empty()&&pos[3].empty())
{
printf("-1");
return 0;
}
if(pos[3].empty()) ans++,cnt-=pos[2].front(),pos[2].erase(pos[2].begin());
else cnt-=pos[3].front(),pos[3].erase(pos[3].begin());
for(int c:pos[3])
cnt-=c;
for(int c:pos[1])
cnt-=c;
pos[3].clear();
pos[1].clear();
if(cnt<=0)
{
printf("%lld",ans);
return 0;
}
for(int i=1;i<=pos[0].size();i++)
s0[i]=s0[i-1]+pos[0][i-1];
for(int i=1;i<=pos[2].size();i++)
s2[i]=s2[i-1]+pos[2][i-1];
long long res=INF;
for(int i=pos[0].size(),j=0;i>=0;i--)
{
while(j<=pos[2].size()&&s0[i]+s2[j]<cnt) j++;
if(j<=pos[2].size()) res=min(res,2LL*i+j);
}
printf("%lld",ans+res);
return 0;
}
F - Division into Multiples
首先可以将 \(A,B,C\) 转化为两两互质的形式。
考虑每组 \(x,y\) 需要满足的条件,即为 \(Ax+By\equiv 0 \pmod C\),令 \(D\equiv \frac{A}{B} \pmod C\) 方程的通解为:
考虑计算出可能成为答案的那些解 \((x,y)\)。
考虑一个高为 \(D\),宽为 \(C\) 的网格,初始位于 \((0,0)\),每次向右上前进一格。当 \(x=C\) 的时候令 \(x=0\),当 \(y=D\) 的时候令 \(y=0\)。当 \(y=0\) 时记录这时的 \(x\),\((\frac{now}{D},C−x)\) 即为一个可能的解,\(now\) 为前进的步数。
不妨令 \(C\ge D\),\(C< D\) 的时候同理。先将 \((C,0)\) 加入可能的解中,我们接下来就不需要考虑 \(x< H\) 的情况,那么就可以将宽缩小为 \(W-H\),大概跟欧几里得差不多吧。
观察上面的算法,将 \((x,y)\) 若按 \(x\) 排序,则是若干个等差数列的形式,我们只需要将端点记录下来,且满足 \(x_i-x_{i-1}\le x_{i+1}-x_i,y_i-y_{i-1}\ge y_{i+1}-y_i\)。
因为每次矩形的高一定会越来越小,故一定满足 \(y_i-y_{i-1}\ge y_{i+1}-y_i\);相反的,也满足 \(x_i-x_{i-1}\le x_{i+1}-x_i\)。
将所有的 \((x_i,y_i)\) 的图像绘制出来,可以发现这是一个下凸包。
可以发现,肯定选两种相邻或相同的 \((x,y)\) 组成是最优的。因为如果选了 \(i,j\) 这两个二元组(\(i,j\) 表示选的二元组的下标)满足 \(j-i\ge 2\),显然将 \(i,j\) 贴近更优秀。所以我们肯定是选一条线段和下一个条线段的第一个点中的若干个。
那么就可以二分答案,对于每天线段判断是否可以选 \(mid\) 个点,令当前线段的左上角为 \((x_i,y_i)\),下一条线段的左上角为 \((x_j,y_j)\),需要满足的条件为:
将两式转化一下:
将相加,可得:
代码:
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int T;
int ex_gcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
int d=ex_gcd(b,a%b,x,y);
int tmp=x;
x=y,y=tmp-a/b*y;
return d;
}
int getinv(int A,int MOD)
{
int x,y;
int d=ex_gcd(A,MOD,x,y);
if(d!=1) return -1;
return (x+MOD)%MOD;
}
int a,x,b,y,c;
int d;
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
vector<pair<int,int> >pos;
vector<int>cnt;
void getpos()
{
pos.clear(),cnt.clear();
int W=c,H=d;
int now=0;
while(true)
{
int r=W/H;
pos.push_back(make_pair(1LL*now*getinv(d,c)%c,now%c==0?c:(c-now+c)%c)),cnt.push_back(r);
now+=r*H;
W%=H;
if(W==0) break;
H%=W;
if(H==0) H=W;
}
pos.push_back(make_pair(c,0));
return;
}
void solve()
{
scanf("%d%d%d%d%d",&a,&x,&b,&y,&c);
int G;
G=gcd(a,b),a/=G,b/=G,c/=gcd(G,c);
G=gcd(a,c),a/=G,c/=G,y/=G;
G=gcd(b,c),b/=G,c/=G,x/=G;
a%=c,b%=c;
if(c==1)
{
printf("%d\n",x+y);
return;
}
d=1LL*a*getinv(b,c)%c;
getpos();
int ans=0;
for(size_t i=0;i<cnt.size();i++)
{
int xl=pos[i].first,yl=pos[i].second,xr=pos[i+1].first,yr=pos[i+1].second;
int dx=(xr-xl)/cnt[i],dy=(yl-yr)/cnt[i];
int l=0,r=x+y,res=0;
while(l<=r)
{
int mid=((long long)l+r)/2;
auto check=[=](int mid)
{
long long p=(x-1LL*xl*mid)/dx,q=(y-1LL*yr*mid)/dy;
return (x-1LL*xl*mid)>=0&&(y-1LL*yr*mid)>=0&&p+q>=1LL*cnt[i]*mid;
};
if(check(mid)) res=mid,l=mid+1;
else r=mid-1;
}
ans=max(ans,res);
}
printf("%d\n",ans);
return;
}
int main()
{
scanf("%d",&T);
while(T--)
solve();
return 0;
}
- AtCoder Contest Grand 045atcoder contest grand 045 authentic atcoder contest grand histogram atcoder contest grand negative atcoder contest grand atcoder contest grand 022 atcoder contest grand 001 atcoder contest radius grand atcoder contest descent grand atcoder contest grand 019 atcoder contest grand 017