AtCoder Grand Contest 015&016

015D A or...or B Problem

题目描述

点此看题

解法

我都能想到的 \(\tt observation\) 是:我们可以去掉 \(l,r\) 二进制中相同的前缀,那么剩下的最高位 \(r\) 一定是 \(1\)\(l\) 一定是 \(0\),这样做的理由也很简单,就是边界是 \(2^k\) 得到的结果是易于考虑的。

那么现在的基础是两个部分 \([l,2^{id}),[2^{id},r]\),我们考虑两边自己或的结果,以及两边联合或的结果。对于两边自己或,发现结果是 \([l,2^{id})\)\([2^{id},2^{id}+2^{k})\),其中 \(k\)\(r\) 的次高位。

两边联合或的结果可以通过两边自己或的结果得出,发现就是 \([2^{id}+l,2^{id+1})\),然后我们把这三个区间取并即可。

总结

统一形式:题目给出的是区间,那么用区间表示结果可能会得到简化。

#include <cstdio>
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int l,r,id,k,ans;
signed main()
{
	l=read();r=read();
	if(l==r) {puts("1");return 0;}
	for(int i=60;i>=0;i--)
	{
		if((r>>i&1) && !(l>>i&1))
			{id=1ll<<i;break;}
		int up=(1ll<<i)-1;
		l&=up;r&=up;
	}
	int x=r-id,k=1;
	for(int i=0;i<60;i++)
		if(x>>i&1) k=1ll<<i+1;
	ans+=id-l;
	ans+=k+id-l;
	if(k>l) ans-=k-l;
	printf("%lld\n",ans);
}

015F Kenus the Ancient Greek

题目描述

点此看题

解法

翻译了官方题解,补充了很多地方的证明,应该是比较严谨的中文题解了。

下文只考虑 \(i<j\) 的情况,\(i>j\) 可以对称地解决。

首先考虑怎么解决第一问,考虑 \(f_0=f_1=1,f_{k}=f_{k-1}+f_{k-2}(k>1)\) 的斐波那契数列,那么迭代次数为 \(k(k>0)\) 的最小二元组一定是 \((f_{k+1},f_{k+2})\)

我们考虑缩小研究数对的范围,设 \(g(n,m)=\max_{i\leq n,j\leq m}f(i,j)\),称 对子 \(k\) 为满足 \(f(i,j)=g(i,j)=k\) 的数对 \((i,j)\),那么对子 \(k\) 的个数就是第二问的答案,并且只有对子才可能成为答案

发现对子在经过少量的 \(\tt gcd\) 操作之后会快速统一形式,具体来说,构造 基对 为满足 \(i,j\leq f_{k+2}\) 的对子 \((i,j)\),我们可以证明任意对子在一步操作之后都会变成基对:

考虑对子 \(f(x,px+y)=k+1\),那么一步操作之后会变成基对 \(f(y,x)=k\)

使用反证法,假设 \((y,x)\) 不是基对,那么 \(x>f_{k+2}\),而我们知道 \(f_{k+2}\leq x,f_{k+3}\leq px+y\),这说明 \(g(x,px+y)\geq k+2\),这和 \((x,px+y)\) 是对子矛盾,所以对子一步操作会变成基对。

由于基对被限制在了很小的范围中,所以基对的数量是很少的。可以递推地求出基对,\(k=1\) 时基对是 \((1,2),(1,3)\),从 \(k-1\) 推到 \(k\) 时,把所有 \((x,y)\) 变成 \((y,x+y)\),此外再增加 \((f_k,f_{k+2})\) 即可,这样求的原因是根据结论,这一层的基对只能从上一层的基对扩展而来,而又要满足 \(i,j\leq f_{k+2}\)

在计算答案的时候,我们考虑基对 \((x,y)\) 可以扩展成对子 \((x,px+y)\),那么取出这一层的所有基对(除了最后一个,因为会算重),然后用除法即可计算扩展成多少对子,注意 \(k=1\) 需要特判。

时间复杂度 \(O(T\cdot \log n)\)

总结

统一形式需要很强的观察能力,我没有信心能自己弄出这个观察,但是保留有效状态却是可以学习的,比如本题就利用了这个技巧定义出了对子。

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 1005;
#define pb push_back
#define int long long
#define pii pair<int,int>
const int MOD = 1e9+7;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,k,lim,f[M];vector<pii> v[M];
void work()
{
	int x=read(),y=read(),n=1,ans=0;
	if(x>y) swap(x,y);
	while(n+2<=k && f[n+1]<=x && f[n+2]<=y) n++;
	for(pii t:v[n])
	{
		if(t.first<=x && t.second<=y)
			ans+=(y-t.second)/t.first+1;
		if(t.first<=y && t.second<=x)
			ans+=(x-t.second)/t.first+1;
		ans%=MOD;
	}
	if(n==1) ans=(ans+x)%MOD;
	printf("%lld %lld\n",n,ans);
}
signed main()
{
	T=read();k=1;lim=1e18;f[0]=f[1]=1;
	while(f[k]+f[k-1]<=lim)
		k++,f[k]=f[k-1]+f[k-2];
	v[1].pb({1,2});
	for(int i=2;i<=k;i++)
	{
		for(pii x:v[i-1])
			v[i].pb({x.second,x.first+x.second});
		v[i].pb({f[i+1],f[i+1]+f[i-1]});
	}
	while(T--) work();
}

016E Poor Turkeys

题目描述

点此看题

解法

竟然自己把一道思维题想明白了,可喜可贺,可喜可贺。

有一个关键的 \(\tt observation\) 是:假设 \(x\) 是必须生存的,如果出现了询问 \((x,y)\),那么到这个人的时候 \(y\) 必须要为 \(x\) 献身,所以可以把限制转移为,在之前的所有询问中 \(y\) 是必须生存的

为了充分地考虑所有限制,我们从后往前扫描所有询问。假设现在要求 \(a,b\) 是否能共存,那么初始设置成 \(a,b\) 必须生存的,假设现在拿到了询问 \((x,y)\),我们来讨论一下它的影响:

  • 如果 \(x,y\) 都没有必须生存的要求,那么此询问无影响。
  • 如果 \(x\) 是必须生存的,那么把 \(y\) 设置成必须生存的。
  • 如果 \(y\) 是必须生存的,那么把 \(x\) 设置成必须生存的。
  • 如果 \(x,y\) 都是必须生存的,直接判定为不合法然后退出扫描。

现在我们得到了一个 \(O(n^2m)\) 的算法,考虑优化,可以使用拆分法,因为 \(a,b\) 的关系只有设置初值这一部分。具体来说就是求出 \(f[i][j]\) 表示初始把 \(i\) 设置成必须生存的,\(j\) 是否是必须生存的,这个可以直接 \(O(nm)\) 地扫描出来。

然后考虑怎么判断 \(a,b\) 是否能共存?发现就是不能存在一个 \(i\),使得 \(f[a][i]=f[b][i]=1\),因为如果存在这样的 \(i\),就一定存在一次询问 \((a,i)/(b,i)\),使得它们都是必须生存的。

那么直接枚举判断即可,时间复杂度 \(O(nm+n^3)\),注意初始扫描时也要判断 \(i\) 是否必死。

#include <cstdio>
const int N = 405;
const int M = 100005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,ans,a[M],b[M],f[N][N],d[N];
signed main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)
		a[i]=read(),b[i]=read();
	for(int i=1;i<=n;i++)
	{
		f[i][i]=1;
		for(int j=m;j>=1;j--)
		{
			int x=f[i][a[j]],y=f[i][b[j]];
			if(x && y) {d[i]=1;break;}
			if(x) f[i][b[j]]=1;
			if(y) f[i][a[j]]=1;
		}
	}
	for(int i=1;i<=n;i++) if(!d[i])
		for(int j=i+1;j<=n;j++) if(!d[j])
		{
			int h=1;
			for(int k=1;k<=n;k++)
				if(f[i][k] && f[j][k])
					{h=0;break;}
			ans+=h;
		}
	printf("%d\n",ans);
}

016F Games on DAG

题目描述

点此看题

解法

正难则反,考虑点 \(1,2\)\(\tt SG\) 函数相同的情况,然后拿 \(2^m\) 去减即可。

观察数据范围就知道大概要做状压 \(dp\),设 \(f[S]\) 表示只考虑导出子图 \(S\),点 \(1,2\)\(\tt SG\) 函数相同的方案数。考虑按照 \(\tt SG\) 函数的大小关系分层,转移枚举 \(\tt SG\) 函数等于 \(0\) 的集合(相当于枚举放在全图上 \(\tt SG\) 函数最小的集合)

枚举子集 \(T\) 表示 \(\tt SG\) 函数非 \(0\) 的集合,那么 \(S\setminus T\) 就表示 \(\tt SG\) 函数为 \(0\) 的集合。\(T\)\(S\setminus T\) 的边,对于每个 \(x\in T\) 至少要连向 \(S\setminus T\) 一条边;\(S\setminus T\)\(T\) 的边,由于不影响 \(\tt SG\) 函数的计算可以任意连;\(S\setminus T\) 内部的边都不能连。

还要考虑 \(1,2\) 的归属情况。如果 \(1,2\) 都在 \(T\) 中,那么转移到状态 \(f[T]\);否则都在 \(S\setminus T\) 中,它们的 \(\tt SG\) 函数值已经相同了,所以 \(T\) 集合内部的边就可以任意定向,不需要转移到子问题了。

时间复杂度 \(O(n\cdot 2^n)\)

总结

关键问题还是确定转移顺序,图规划问题中转移顺序尤为重要。

#include <cstdio>
const int M = 15;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[M][M],c[1<<M][M],f[1<<M],w[505];
void trans(int s,int t)
{
	int x=1;
	if(t&1)//1,2 all in T
	{
		for(int i=0;i<n;i++) if(s>>i&1)
		{
			if(t>>i&1) x=x*(w[c[s^t][i]]-1)%MOD;
			else x=x*w[c[t][i]]%MOD;
		}
		f[s]=(f[s]+f[t]*x)%MOD;
	}
	else//1,2 all in S \ T
	{
		for(int i=0;i<n;i++) if(s>>i&1)
		{
			if(t>>i&1)
				x=x*(w[c[s^t][i]]-1)%MOD*w[c[t][i]]%MOD;
			else x=x*w[c[t][i]]%MOD;
		}
		f[s]=(f[s]+x)%MOD;
	}
}
signed main()
{
	n=read();m=read();w[0]=1;
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		a[u-1][v-1]++;
		w[i]=w[i-1]*2%MOD;
	}
	for(int s=1;s<(1<<n);s++)
	{
		int j=0;while(!(s>>j&1)) j++;
		for(int i=0;i<n;i++)
			c[s][i]=c[s-(1<<j)][i]+a[i][j];
	}
	for(int s=1;s<(1<<n);s++) if((s&3)==3)
	{
		f[s]=1;//all sg=0
		for(int t=s&(s-1);t;t=(t-1)&s)
			if((t&1)==(t>>1&1)) trans(s,t);
	}
	printf("%lld\n",(w[m]-f[(1<<n)-1]+MOD)%MOD);
}
posted @ 2022-03-15 17:52  C202044zxy  阅读(51)  评论(0编辑  收藏  举报