AtCoder Grand Contest 045

A - Xor Battle

可以发现,从后往前扫,遇到一个 1 找后面是否有若干个 0 的位置的 ai 与当前位置的异或和相等,用线性基维护一下就好了。


代码:

#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 看成 +10 看成 1,做一个前缀和 s,问题就变成了求 s 的最大值减最小值的值最小。

考虑枚举 s 的最小值 min,问题就变成了要使得 max 最小。考虑从前往后决定每个位置是否将 ? 改成 0,对 s 产生的影响即为后缀 2,可以证明这是最优的。

m=minsi,可以发现,当 min=m2 时,不可能比 min=m 时优,故我们只需要求 min=m,m1 的情况就好了。


代码:

#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

不妨令 AB,这两种情况是等价的,因为我们可以开始将整个序列染成 1

考虑一个最后的字符串是否能被达到,可以将所有的长度 A0 段染成 1,如果串内是否有长度 B1 段的话肯定是合法的,因为我们可以每次拓展一个 0,否则是不合法的。

合法的方案很难算,不妨计算不合法的方案。

fi,0/1 表示长度为 i,以 0/1 结尾,段内没有长度 <A0 的方案数,枚举上一个段的位置转移。

dpi,0/1 表示长度为 i,以 0/1 结尾,段内没有长度 <A0 和长度 <B1 的方案数,枚举上一个段的位置转移。

注意最后的长度 <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

可以发现,不合法的情况为环中一个点都没有被点亮,或者有个自环他去点了。

考虑枚举第一个 pi=iiA 的位置,如果不存在则 i=A+1。可以发现对于所有 A<jnj 所在的环至少存在一个小于 i 的点。

对于 i,我们需要满足 j<i 的所有位置 pjj,这个可以容斥解决。可以将点分成几类:

  • pi=i 的点,可以将这些点去掉。
  • 1i<t 的点,记为 a 个。
  • A<in 的点。记为 b 个。每个点所在环中都要至少存在一个小于 i 的点。
  • t<iA 的点。记为 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

考虑 Ci=1 的情况,将原图上连一条 AiBi 的边,可以分成几类:

  1. 只包含一个自环;
  2. 只包含一个长度大于等于 2 的简单环;
  3. 联通块的边数大于点数。

当存在第 2 种联通块的话,答案就是 1;否则答案就是 PP 表示图中的 AiBi 的数量。

考虑 Ci1 的情况,我们可以调整使得有解。将一个当前联通块外面的点放进来,然后再处理。可以发现,令 cnt 表示第二种联通块的数量,我们至少需要额外多 cnt 步。

考虑从外面移入一个球 x,分几种情况讨论当前点多出的步数:

  1. x 在第 1 种联通块内,对联通块数贡献为 (Cx2),需要多花费两步。
  2. x 在第 2 种联通块内,对联通块数贡献为 (Cx1),不需要多花费步数。
  3. x 在第 3 种联通块内,且 Ax=Bx,对联通块数贡献为 (Cx1),需要多花费一步。
  4. x 在第 3 种联通块内,且 AxBx,对联通块数贡献为 (Cx1),不需要多花费步数。

对于 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+By0(modC),令 DAB(modC) 方程的通解为:

{xk(modC)ykD(modC)

考虑计算出可能成为答案的那些解 (x,y)

考虑一个高为 D,宽为 C 的网格,初始位于 (0,0),每次向右上前进一格。当 x=C 的时候令 x=0,当 y=D 的时候令 y=0。当 y=0 时记录这时的 x(nowD,Cx) 即为一个可能的解,now 为前进的步数。

不妨令 CDC<D 的时候同理。先将 (C,0) 加入可能的解中,我们接下来就不需要考虑 x<H 的情况,那么就可以将宽缩小为 WH,大概跟欧几里得差不多吧。

观察上面的算法,将 (x,y) 若按 x 排序,则是若干个等差数列的形式,我们只需要将端点记录下来,且满足 xixi1xi+1xi,yiyi1yi+1yi

因为每次矩形的高一定会越来越小,故一定满足 yiyi1yi+1yi;相反的,也满足 xixi1xi+1xi

将所有的 (xi,yi) 的图像绘制出来,可以发现这是一个下凸包。

可以发现,肯定选两种相邻或相同的 (x,y) 组成是最优的。因为如果选了 i,j 这两个二元组(i,j 表示选的二元组的下标)满足 ji2,显然将 i,j 贴近更优秀。所以我们肯定是选一条线段和下一个条线段的第一个点中的若干个。

那么就可以二分答案,对于每天线段判断是否可以选 mid 个点,令当前线段的左上角为 (xi,yi),下一条线段的左上角为 (xj,yj),需要满足的条件为:

{k=1midxi+zkΔxXk=1midyj+(cntzk1)ΔyY

将两式转化一下:

{k=1midzkXximidΔxk=1mid(cntzk1)YyjmidΔy

将相加,可得:

k=1midzk+k=1mid(cntzk1)=mid(cnt1)XximidΔx+YyjmidΔy


代码:

#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;
}
posted @   Zhou_JK  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示