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);
}