2023.8 做题笔记
【NOI 2023】方格染色
先把同一横/竖/斜线上所有区间合并成两两不交的区间,会很好处理。
只有操作 \(1,2\) 是很好处理的,对一维做扫描线,然后减去第二维重复的位置数量。
操作 \(3\) 只有 \(5\) 次,可以暴力枚举每一条横/竖线判断是否有交。
复杂度 \(O(q \log q)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define int long long
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int t[100005];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int d)
{
assert(x<=100000);
for(int i=x;i<=100000;i+=lowbit(i)) t[i]+=d;
}
int query(int x)
{
int res=0;
for(int i=x;i>=1;i-=lowbit(i)) res+=t[i];
return res;
}
map <int,int> mx,may,maz;
int n,m,q,idx,idy,idz,realx[100005],realy[100005],realz[100005];
vector <pii > vx[100005],vy[100005],vz[100005];
vector <pii > qwq(vector <pii > vec)
{
sort(vec.begin(),vec.end());
vector <pii > res;
for(int i=0;i<vec.size();i++)
{
int L=vec[i].fi,R=vec[i].se;
int j=i;
while(j+1<vec.size()&&vec[j+1].fi<=R+1) j++,R=max(R,vec[j].se);
res.pb(mp(L,R)),i=j;
}
return res;
}
map <pii,bool> vis;
void solve()
{
cin>>n>>m>>q;
ll ans=0;
while(q--)
{
int op,x1,y1,x2,y2;
cin>>op>>x1>>y1>>x2>>y2;
if(op==1)
{
if(!may[y1]) may[y1]=++idy,realy[idy]=y1;
vy[may[y1]].pb(mp(min(x1,x2),max(x1,x2)));
// ans+=1LL*abs(x1-x2)+1;
}
if(op==2)
{
if(!mx[x1]) mx[x1]=++idx,realx[idx]=x1;
vx[mx[x1]].pb(mp(min(y1,y2),max(y1,y2)));
// ans+=1LL*abs(y1-y2)+1;
}
if(op==3)
{
int z=y1;
z-=x1-1;
if(!maz[z]) maz[z]=++idz,realz[idz]=z;
// cout<<"3: "<<maz[z]<<"\n";
vz[maz[z]].pb(mp(y1-z,y2-z));
// cerr<<x1<<" "<<y1<<" "<<x2<<" "<<y2<<" "<<z<<" "<<maz[z]<<" : "<<y1-z<<" "<<y2-z<<"\n";
// ans+=1LL*abs(y1-y2)+1;
}
}
for(int i=1;i<=idx;i++)
{
vx[i]=qwq(vx[i]);
for(int j=0;j<vx[i].size();j++) ans+=1LL*(vx[i][j].se-vx[i][j].fi+1);
}
for(int i=1;i<=idy;i++)
{
vy[i]=qwq(vy[i]);
for(int j=0;j<vy[i].size();j++) ans+=1LL*(vy[i][j].se-vy[i][j].fi+1);
}
for(int i=1;i<=idz;i++)
{
vz[i]=qwq(vz[i]);
for(int j=0;j<vz[i].size();j++) ans+=1LL*(vz[i][j].se-vz[i][j].fi+1);
}
vector <array<int,4> > vec;
vector <int> val;
for(int i=1;i<=idx;i++) val.pb(realx[i]);
sort(val.begin(),val.end()),val.resize(unique(val.begin(),val.end())-val.begin());
for(int i=1;i<=idx;i++) for(int j=0;j<vx[i].size();j++)
{
int u=lower_bound(val.begin(),val.end(),realx[i])-val.begin()+1;
vec.pb({vx[i][j].fi,0,1,u}),vec.pb({vx[i][j].se+1,0,-1,u});
}
for(int i=1;i<=idy;i++) for(int j=0;j<vy[i].size();j++)
{
int L=lower_bound(val.begin(),val.end(),vy[i][j].fi)-val.begin()+1;
int R=upper_bound(val.begin(),val.end(),vy[i][j].se)-val.begin();
vec.pb({realy[i],1,L,R});
}
sort(vec.begin(),vec.end());
for(int i=0;i<vec.size();i++)
{
if(vec[i][1]==0) update(vec[i][3],vec[i][2]);
else if(vec[i][2]<=vec[i][3]) ans-=1LL*(query(vec[i][3])-query(vec[i][2]-1));
}
// cerr<<ans<<"\n";
for(int i=1;i<=idz;i++) for(int j=0;j<vz[i].size();j++)
{
int z=realz[i];
int x1=1+vz[i][j].fi,y1=z+vz[i][j].fi;
int x2=1+vz[i][j].se,y2=z+vz[i][j].se;
// cerr<<x1<<" "<<y1<<" "<<x2<<" "<<y2<<" "<<idx<<" "<<idy<<"\n";
vis.clear();
for(int l=1;l<=idx;l++)
{
int x=realx[l];
if(x1<=x&&x<=x2)
{
int y=y1+x-x1;
int p=lower_bound(vx[l].begin(),vx[l].end(),mp(y+1,-1LL))-vx[l].begin()-1;
if(p>=0&&vx[l][p].fi<=y&&y<=vx[l][p].se&&!vis[mp(x,y)]) vis[mp(x,y)]=1,ans--;
}
}
for(int l=1;l<=idy;l++)
{
int y=realy[l];
if(y1<=y&&y<=y2)
{
int x=x1+y-y1;
int p=lower_bound(vy[l].begin(),vy[l].end(),mp(x+1,-1LL))-vy[l].begin()-1;
if(p>=0&&vy[l][p].fi<=x&&x<=vy[l][p].se&&!vis[mp(x,y)]) vis[mp(x,y)]=1,ans--;
}
}
}
// cerr<<ans<<"\n";
cout<<ans<<"\n";
}
signed main()
{
// freopen("color.in","r",stdin);
// freopen("color.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
int dirt;
cin>>dirt;
// cin>>_;
while(_--) solve();
return 0;
}
【NOI 2023】桂花树
特别妙的 ad-hoc 题,答案竟然和树的形态是无关的!
先考虑 \(k=0\),第一个条件可以看做 \(T\) 是 \(T'\) 的虚树,所以一棵合法的树的生成过程可以看做,按编号从小到大插入点,保证每个点只有 \(\le 1\) 个子树,也就是说要么放在一条边的中间,要么作为叶子挂在一个点上,令 \(x\) 为目前 \(T\) 的节点数,则有 \((2x-1)\) 种插入的方案,故答案为 \(\prod_{i=0}^{m-1} (2(n+i)-1)\)。
再考虑一般情况,此时可能会存在形如:
w
/
/
y
/ \
/ \
x z
的情况,其中 \(1 \le w,x\le n,n+1 \le z \le y \le n+m,y-z \le k\),这样也是合法的。
同样的,从小到大在 \(T\) 上插入点,这种情况在插入 \(z\) 的时候考虑,方法是预留出 \(y\) 的位置,在插入后续节点的时候填上。
令 \(dp(i,mask)\) 表示,目前插入了前 \(i\) 个点,\([i+1,i+k]\) 中 \(mask\) 里面的点被预留成为某个 \(z\) 对应的 \(y\),的方案数。转移考虑三种情况:插在边中间或挂一个叶子;找一条边并选择一个后面的节点 \(y\) 作为它的父亲;填入 \(mask\) 中的某一位。系数都是关于目前点数的一个很简单的式子。
复杂度 \(O(m2^kk)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int dp[3005][1<<10],ppc[1<<10];
int n,m,k,dirt;
void solve()
{
cin>>n>>m>>k;
for(int i=1;i<n;i++) cin>>dirt;
for(int i=0;i<(1<<k);i++) ppc[i]=__builtin_popcount(i);
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=0;i<m;i++) for(int mask=0;mask<(1<<k);mask++) if(dp[i][mask])
{
if(mask&1) dp[i+1][mask>>1]=(dp[i+1][mask>>1]+dp[i][mask])%mod;
else
{
int N=n+i+ppc[mask];
dp[i+1][mask>>1]=(dp[i+1][mask>>1]+dp[i][mask]*(2*N-1))%mod;
for(int j=0;j<k;j++)
{
if((mask>>1)&(1<<j)) continue;
dp[i+1][(mask>>1)|(1<<j)]=(dp[i+1][(mask>>1)|(1<<j)]+dp[i][mask]*(N-1))%mod;
}
}
}
cout<<dp[m][0]<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
cin>>_>>_;
while(_--) solve();
return 0;
}
【NOI 2023】贸易
先观察一下 \(u\) 到 \(v\) 的最短路长什么样,一定是 \(u\) 向上走若干步到达某个祖先 \(w\),然后以 \(w\) 为起点,在 \(w\) 的子树内求 \(w\) 到 \(v\) 的最短路。
计算答案的时候,可以 dfs 这棵树,访问到这个点的时候算这个点到所有点的最短路之和,过程大概是:进入这个点的时候跑这个点子树内的单源最短路,然后更新所有点的最短路,离开这个点的时候需要把更新的内容撤销掉,由于我们只需要查询最小值,用两个 priority_queue 维护就行。
复杂度 \(O(2^n poly (n))\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int dep[300005],dist[300005],vis[300005];
vector <pii > g[300005];
priority_queue <int> add[300005],del[300005];
int n,m;
int ans,sum=0,cnt=0;
vector <int> sub;
void dfs1(int u)
{
if(u>n) return;
sub.pb(u);
dfs1(u<<1),dfs1(u<<1|1);
}
void upd(int x,int val,int op)
{
if(op==1)
{
if(add[x].size()) cnt--,sum+=add[x].top();
add[x].push(-val);
cnt++,sum-=add[x].top();
}
else
{
if(add[x].size()) cnt--,sum+=add[x].top();
del[x].push(-val);
while(del[x].size()&&add[x].top()==del[x].top()) add[x].pop(),del[x].pop();
if(add[x].size()) cnt++,sum-=add[x].top();
}
}
void dij(int s,int op)
{
sub.clear();
dfs1(s);
for(int i=0;i<sub.size();i++) dist[sub[i]]=INF,vis[sub[i]]=0;
priority_queue <pii > pq;
pq.push(mp(0,s)),dist[s]=0;
while(pq.size())
{
int u=pq.top().se;
pq.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i].fi,w=g[u][i].se;
if(v<s) continue;
dist[v]=min(dist[v],dist[u]+w),pq.push(mp(-dist[v],v));
}
}
for(int i=0;i<sub.size();i++) if(dist[sub[i]]<INF) upd(sub[i],dist[sub[i]]-dep[s],op);
}
void dfs0(int u)
{
if(u>n) return;
dij(u,1);
// cout<<u<<" "<<sum<<" "<<dep[u]<<" "<<cnt<<"\n";
ans=(ans+sum)%mod,ans=(ans+dep[u]%mod*cnt)%mod;
dfs0(u<<1),dfs0(u<<1|1);
dij(u,0);
}
void solve()
{
cin>>n>>m;
n=(1<<n)-1;
for(int i=2;i<=n;i++)
{
int x;
cin>>x;
g[i].pb(mp(i/2,x)),dep[i]=dep[i/2]+x;
}
while(m--)
{
int u,v,w;
cin>>u>>v>>w;
g[u].pb(mp(v,w));
}
dfs0(1);
cout<<ans<<"\n";
}
signed main()
{
// freopen("trade.in","r",stdin);
// freopen("trade.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【NOI 2023】字符串
先将 \(R(S)\) 拼到 \(S\) 的后面,令 \(T=S+R(S)\),跑 \(T\) 的后缀数组。
那么一个 \(l\) 合法,在大多数情况下能刻画成比较两个后缀排名的大小,每个询问的答案是一个二维偏序的形式。
什么情况下不对,当且仅当 \(s[i,i+2l-1]\) 是回文串的情况下可能不对,写出关于 \(p=i+l-1\) 的三个条件:后缀排名的限制;回文半径的限制和 \(r\) 的限制,然后你会发现在后缀排名里是关于 \(i+p\) 的一个东西,这个特别不好处理。再观察到因为是回文,可以把关于后缀排名中的两个参数同时加上 \(l\),这样是等价的,因为在开头同时删去一段一样的字符,不改变字典序关系。
复杂度除预处理后缀数组之外,只有二维数点,复杂度 \(O(n \log n)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
int nn,kk,Rk[400005];
bool cmp(int i,int j)
{
if(Rk[i]!=Rk[j]) return Rk[i]<Rk[j];
else
{
int ri=i+kk<=nn?Rk[i+kk]:-1;
int rj=j+kk<=nn?Rk[j+kk]:-1;
return ri<rj;
}
}
struct SA
{
char s[400005];
int n,rk[400005],tmp[400005],sa[400005],k;
void build(string str)
{
n=str.size();
for(int i=0;i<n;i++) s[i]=str[i];
for(int i=0;i<=n;i++)
{
sa[i]=i;
rk[i]=i<n?s[i]:-1;
}
for(k=1;k<=n;k*=2)
{
nn=n,kk=k;
for(int i=0;i<=n;i++) Rk[i]=rk[i];
sort(sa,sa+n+1,cmp);
tmp[sa[0]]=0;
for(int i=1;i<=n;i++) tmp[sa[i]]=tmp[sa[i-1]]+(cmp(sa[i-1],sa[i])?1:0);
for(int i=0;i<=n;i++) rk[i]=tmp[i];
}
for(int i=n;i>=1;i--) rk[i]=rk[i-1];
}
}sa;
char s[100005];
int n,q;
struct Hashing
{
int mod,bse;
int pw[100005];
int H1[100005],H2[100005];
void init()
{
// mod=M,bse=B;
pw[0]=1;
for(int i=1;i<=n;i++) pw[i]=1LL*pw[i-1]*bse%mod;
for(int i=1;i<=n;i++) H1[i]=1LL*bse*H1[i-1]%mod+(s[i]-'a'),H1[i]%=mod;
for(int i=1;i<=n;i++) H2[i]=1LL*bse*H2[i-1]%mod+(s[n-i+1]-'a'),H2[i]%=mod;
}
int query1(int l,int r)
{
return (H1[r]-1LL*H1[l-1]*pw[r-l+1]%mod+mod)%mod;
}
int query2(int l,int r)
{
l=n-l+1,r=n-r+1;
if(l>r) swap(l,r);
return (H2[r]-1LL*H2[l-1]*pw[r-l+1]%mod+mod)%mod;
}
}s1,s2;
struct BIT
{
int t[200005];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int d)
{
for(int i=x;i<=200000;i+=lowbit(i)) t[i]+=d;
}
int query(int x)
{
int res=0;
for(int i=x;i>=1;i-=lowbit(i)) res+=t[i];
return res;
}
}t0,t1;
int ans[100005],d[100005];
vector <array<int,3> > que;
bool cmp1(array <int,3> x,array <int,3> y)
{
return sa.rk[x[0]]>sa.rk[y[0]];
}
void solve()
{
cin>>n>>q;
cin>>(s+1);
s1.mod=998244353,s1.bse=127;
s2.mod=1000000009,s2.bse=131;
s1.init(),s2.init();
for(int i=1;i<=n;i++)
{
int L=1,R=min(i,n-i),res=0;
while(L<=R)
{
int mid=(L+R)>>1;
if(s1.query1(i-mid+1,i)!=s1.query2(i+1,i+mid)) R=mid-1;
else if(s2.query1(i-mid+1,i)!=s2.query2(i+1,i+mid)) R=mid-1;
else res=mid,L=mid+1;
}
d[i]=res;
}
string tmp;
for(int i=1;i<=n;i++) tmp+=s[i];
for(int i=n;i>=1;i--) tmp+=s[i];
sa.build(tmp);
que.clear();
for(int i=1;i<=q;i++)
{
int u,r;
cin>>u>>r;
que.pb({u,r,i});
ans[i]=0;
}
memset(t1.t,0,sizeof(t1.t));
memset(t0.t,0,sizeof(t0.t));
vector <pii > con;
con.clear();
for(int i=n+1;i<=n+n;i++) con.pb(mp(sa.rk[i],i));
// for(int i=1;i<=n+n;i++) cout<<sa.rk[i]<<"\n";
sort(con.begin(),con.end()),reverse(con.begin(),con.end());
sort(que.begin(),que.end(),cmp1);
for(int i=0,j=0;i<que.size();i++)
{
int u=que[i][0];
while(j<con.size()&&con[j].fi>sa.rk[u])
{
if(con[j].se%2==0) t0.update(con[j].se,1);
else t1.update(con[j].se,1);
j++;
}
int L=1,R=que[i][1];
L=2*n-u-2*L+2;
R=2*n-u-2*R+2;
swap(L,R);
if(R%2==1) ans[que[i][2]]+=t1.query(R)-t1.query(L-1);
else ans[que[i][2]]+=t0.query(R)-t0.query(L-1);
}
memset(t1.t,0,sizeof(t1.t));
vector <array<int,4> > vec;
for(int p=1;p<n;p++) if(sa.rk[p+1]<sa.rk[2*n-p+1]) vec.pb({d[p]-p,1,p,p});
for(int i=0;i<que.size();i++) vec.pb({-que[i][0]+1,0,que[i][1]+que[i][0]-1,i});
sort(vec.begin(),vec.end()),reverse(vec.begin(),vec.end());
for(int i=0;i<vec.size();i++)
{
// cout<<vec[i][0]<<" "<<vec[i][1]<<" "<<vec[i][2]<<" "<<vec[i][3]<<"\n";
if(vec[i][1]==1) t1.update(vec[i][2],1);
else ans[que[vec[i][3]][2]]-=t1.query(vec[i][2])-t1.query(que[vec[i][3]][0]-1);
}
// sort(que.begin(),que.end());
// con.clear();
// for(int i=1;i<=n;i++) con.pb(mp(d[i]-i,i));
// sort(con.begin(),con.end()),reverse(con.begin(),con.end());
// set <int> S;
// S.clear();
// for(int i=0,j=0;i<que.size();i++)
// {
// int u=que[i][0];
// while(j<con.size()&&con[j].fi>=-u+1) S.insert(con[j].se),j++;
// for(auto it=S.lower_bound(u);it!=S.end()&&(*it)-u+1<=que[i][1];it++)
// {
// int l=(*it)-u+1;
//// cout<<u<<" "<<l<<" "<<sa.rk[u]<<" "<<sa.rk[2*n-i-2*l+2]<<"\n";
// if(sa.rk[u]<sa.rk[2*n-u-2*l+2]) ans[que[i][2]]--;
// }
//
// }
for(int i=1;i<=q;i++) cout<<ans[i]<<"\n";
// cerr<<"ok\n";
}
signed main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
cin>>_>>_;
while(_--) solve();
return 0;
}
【NOI 2023】合并书本
特别感性的做法,不保真。
用一棵二叉树去刻画合并过程,令每一次合并表示把左儿子叠到右儿子上面。
状态可以记成三元组 \(T=\{d,c,S\}\),\(d\) 表示目前的磨损值(深度),\(c\) 表示因为磨损值而产生的代价,\(S\) 表示每一本书的重量对答案贡献的系数,是一个无序集,最后求答案的时候,越小的 \(S_i\) 对应越大的 \(a_i\)。为了方便,我们令它为一个递增序列。
观察到如果存在一个三元组 \(T=\{d,c,S\}\) 能被另一个三元组 \(T'=\{d',c',S'\}\) 偏序,即 \(d' \le d,c' \le c,S'_i \le S_i\),则 \(T\) 可以被舍去,理由显然。
这样虽然最终状态只有 \(2000\) 左右,但转移复杂度很高,需要优化。
最显然的一个优化,\(c \ge 10^{12}\) 的可以舍去,因为在 \(a_i=10^9\) 的情况下可以构造完全二叉树得到更优的答案。
还有一个优化是,\(\max\{S_i\}\) 太大了显然不优,我们只保留 \(\max\{S_i\} \le 7\) 的状态(\(7\) 感觉没啥道理啊,反正是我在能过的情况下不断缩小得到的)。记录状态的时候不记录 \(S_i\),而是记录 \(S_i\) 的权值数组,即记录 \(=0,1,2,\cdots\) 的 \(i\) 分别有几个,比较的时候比较后缀和就行。
同样的,感性理解,我们不会将一个 \(d\) 很大的状态作为左儿子合并上去,令这个 \(d\) 的阈值为 \(7\)(还是我试出来的)。
还有一个实现上面的优化,由于我们需要剪掉被偏序的状态,直接平方暴力枚举是特别慢的。可以先将所有状态按照某种关键字的顺序排序,每次只比较它前面的那个,就可以去掉很多无用的状态。然而对于很多状态排序也是特别慢的,可以在加入一些状态之后,排序,剪枝。最后平方暴力枚举一下剪枝,就特别快了。
Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const ll INF=1e12;
const int LIM=7;
const int LIM_DEP=7;
struct Node
{
int d,S[LIM+1];
ll c;
};
bool cmp(Node x,Node y)
{
if(x.d!=y.d) return x.d<y.d;
if(x.c!=y.c) return x.c<y.c;
for(int i=LIM;i>=0;i--) if(x.S[i]!=y.S[i]||!i) return x.S[i]<y.S[i];
}
bool chk(Node A,Node B) // check B<A
{
if(A.d<B.d) return 0;
if(A.c<B.c) return 0;
for(int i=LIM,sa=0,sb=0;i>=0;i--)
{
sa+=A.S[i],sb+=B.S[i];
if(sa<sb) return 0;
}
return 1;
}
Node merge(Node A,Node B) // move A to B
{
Node C;
C.c=A.c+B.c+(1LL<<A.d)+(1LL<<B.d)-2;
if(A.d>=LIM_DEP)
{
C.c=2LL*INF;
return C;
}
C.d=max(A.d,B.d)+1;
for(int i=0;i<=LIM;i++) C.S[i]=B.S[i]+(i?A.S[i-1]:0);
return C;
}
vector <Node> vec[105],now;
void print(Node x)
{
cout<<x.c<<" "<<x.d<<"\n";
for(int i=0;i<=LIM;i++) cout<<x.S[i]<<" ";
cout<<"\n";
}
int n,a[105];
ll sum[105];
void solve()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n),reverse(a+1,a+1+n);
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+1LL*a[i];
ll ans=INF;
for(int i=0;i<vec[n].size();i++)
{
Node t=vec[n][i];
ll nw=t.c;
// print(t);
// system("pause");
for(int j=0,k=0;j<=LIM;j++) nw+=1LL*j*(sum[k+t.S[j]]-sum[k]),k+=t.S[j];
ans=min(ans,nw);
}
cout<<ans<<"\n";
}
void ins(Node x)
{
if(x.c>INF) return;
now.pb(x);
}
void qwq(int i)
{
sort(now.begin(),now.end(),cmp);
for(int j=0;j<now.size();j++)
{
bool ok=1;
for(int k=(int)(vec[i].size()-1);k<vec[i].size();k++) if(now[j].c>INF||chk(now[j],vec[i][k]))
{
ok=0;
break;
}
if(ok) vec[i].pb(now[j]);
}
swap(vec[i],now),vec[i].clear();
}
void qwq2(int i)
{
// sort(now.begin(),now.end(),cmp);
for(int j=0;j<now.size();j++)
{
bool ok=1;
for(int k=0;k<vec[i].size();k++) if(now[j].c>INF||chk(now[j],vec[i][k]))
{
ok=0;
break;
}
if(ok) vec[i].pb(now[j]);
}
}
signed main()
{
// freopen("C.out","w",stdout);
Node tmp;
tmp.d=0,tmp.c=0;
memset(tmp.S,0,sizeof(tmp.S));
tmp.S[0]=1;
vec[1].pb(tmp);
for(int i=2;i<=100;i++)
{
now.clear();
for(int j=1;j<i;j++)
{
int k=i-j;
if(j>k) break;
if(j!=k)
{
for(int u=0;u<vec[j].size();u++) for(int v=0;v<vec[k].size();v++)
{
// cout<<j<<" "<<k<<" "<<u<<" "<<v<<"\n";
if(!vec[j][u].S[LIM]) ins(merge(vec[j][u],vec[k][v]));
if(!vec[k][v].S[LIM]) ins(merge(vec[k][v],vec[j][u]));
}
}
else
{
for(int u=0;u<vec[j].size();u++) for(int v=u;v<vec[j].size();v++)
{
// cout<<j<<" "<<k<<" "<<u<<" "<<v<<"\n";
if(!vec[j][u].S[LIM]) ins(merge(vec[j][u],vec[j][v]));
if(u!=v&&!vec[j][v].S[LIM]) ins(merge(vec[j][v],vec[j][u]));
}
}
qwq(i);
}
qwq2(i);
// swap(vec[i],now);
// cout<<i<<" "<<vec[i].size()<<"\n";
// system("pause");
// for(int j=0;j<vec[i].size();j++) print(vec[i][j]);
// system("pause");
}
freopen("book.in", "r", stdin);
freopen("book.out", "w", stdout);
// ios::sy]nc_with_stdio(0);
// cin.tie(0);
int _=1;
cin>>_;
while(_--) solve();
return 0;
}
【CF1854D】Michael and Hotel
知道 \(1\) 所在连通块的环之后,只需要问每个点走 \(10^9\) 步后是否在环上就行。
如何找到环上的一个点?从 \(1\) 开始走 \(10^9\) 步,二分找到这个到达的点。
令目前我们知道了环上的一段连续的 \(len\) 个点,如何通过这些点知道环上其它一些点?对于每个点询问走 \(len\) 步能不能落在这一段。
如果 \(len\) 很小,这样做是不如通过 \(\log n\) 的询问二分出某个点的 \(a_i\) 的,令阈值为 \(63\),小的二分,大的倍增。
复杂度 \(O(能过)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
bool ok[505];
int n,nxt[505],tot=0;
int getnxt(int u,int dist)
{
int L=1,R=n;
while(L<R)
{
int mid=(L+R)>>1;
cout<<"? "<<u<<" "<<dist<<" "<<mid-L+1<<" ";
for(int i=L;i<=mid;i++) cout<<i<<" ";
cout<<"\n";
tot++;
fflush(stdout);
int x;
cin>>x;
if(x) R=mid;
else L=mid+1;
}
return L;
}
void solve()
{
cin>>n;
int u=getnxt(1,10000);
ok[u]=1;
int sz=63;
for(int _=0;_<62;_++)
{
int v=getnxt(u,1);
if(ok[v]) sz=inf;
ok[v]=1,u=v;
}
// assert(tot<=9*sz);
bool Flg=0;
while(sz<n)
{
vector <int> que;
for(int i=1;i<=n;i++) if(ok[i]) que.pb(i);
int del=0;
for(int i=1;i<=n;i++) if(!ok[i])
{
cout<<"? "<<i<<" "<<sz<<" "<<que.size()<<" ";
for(int j=0;j<que.size();j++) cout<<que[j]<<" ";
cout<<"\n";
tot++;
fflush(stdout);
int x;
cin>>x;
if(x&&!ok[i]) ok[i]=1,del++;
}
if(del<sz) break;
sz*=2;
}
assert(tot<=1626);
vector <int> que;
for(int i=1;i<=n;i++) if(ok[i]) que.pb(i);
for(int i=1;i<=n;i++) if(!ok[i])
{
cout<<"? "<<i<<" "<<10000<<" "<<que.size()<<" ";
for(int j=0;j<que.size();j++) cout<<que[j]<<" ";
cout<<"\n";
fflush(stdout);
int x;
cin>>x;
if(x) ok[i]=1;
}
vector <int> ans;
for(int i=1;i<=n;i++) if(ok[i]) ans.pb(i);
cout<<"! "<<ans.size()<<" ";
for(int i=0;i<ans.size();i++) cout<<ans[i]<<" ";
cout<<"\n";
fflush(stdout);
}
signed main()
{
// int res=INF,x=-1;
// for(int i=1;i<=500;i++)
// {
// int tot=9*i;
// for(int j=i;j<=500;j*=2)
// {
// tot+=500-j;
// }
// if(tot<res) res=tot,x=i;
// }
// cout<<res<<" "<<x<<"\n";
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【CF1854E】Game Bundles
玄学题。
为了方案数多,我们需要先随机加入很多的 \(1,2\)。
先随机若干个 \(1,2\),由于一个方案只会包含至多一个 \(\ge 31\) 的数,可以认为这些数两两独立。
每次找到一个最小的 \(\ge 31\) 的数,且加入后方案数不超过 \(k\) 的数加入,如果发现不合法就重新随机 \(1,2\)。
实测 \(1,2\) 的数量在 \([36,38]\) 中随机,由于有 \(59,60\) 这种数去进行微调,尤其是 \(60\),表现很不错。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int B=36;
mt19937 rnd(114514);
int dp[65];
int lim;
void solve()
{
cin>>lim;
while(1)
{
B=36+rnd()%3;
vector <int> ans;
memset(dp,0,sizeof(dp));
dp[0]=1;
for(int i=1;i<=B;i++)
{
ans.pb(1+rnd()%2);
for(int j=60;j>=ans.back();j--) dp[j]+=dp[j-ans.back()];
if(dp[60]>lim) break;
if(dp[60]==lim)
{
cout<<ans.size()<<"\n";
for(int j=0;j<ans.size();j++) cout<<ans[j]<<" ";
return;
}
}
if(dp[60]>lim) continue;
// cerr<<"...\n";
while(ans.size()<60&&dp[60]<lim)
{
int ti=0,flg=0;
for(int x=31;x<=60;x++)
{
if(dp[60]+dp[60-x]<=lim)
{
ans.pb(x);
for(int j=60;j>=ans.back();j--) dp[j]+=dp[j-ans.back()];
flg=1;
break;
}
ti++;
}
if(!flg) break;
}
if(dp[60]!=lim) continue;
cout<<ans.size()<<"\n";
for(int j=0;j<ans.size();j++) cout<<ans[j]<<" ";
return;
}
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【AGC030D】Inversion Sum
假设每一次操作 \(1/2\) 的概率执行,\(1/2\) 的概率不执行,计算期望逆序对数,乘 \(2^Q\) 就是答案。
令 \(dp(i,j,k)\) 表示前 \(i\) 次操作之后 \(A_j \lt A_k\) 的概率,一次操作只会影响 \(X,Y\) 所在行/列,把这些行列拿出来暴力算一下就行。
复杂度 \(O(NQ)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int fpow(int x,int b)
{
if(x==0) return 0;
if(b==0) return 1;
int res=1;
while(b>0)
{
if(b&1) res=1LL*res*x%mod;
x=1LL*x*x%mod;
b>>=1;
}
return res;
}
int n,Q,a[3005];
int dp[3005][3005],g[2][3005];
void solve()
{
cin>>n>>Q;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
{
if(a[i]>a[j]) dp[i][j]=1;
else dp[i][j]=0;
}
int inv=fpow(2,mod-2);
for(int _=1;_<=Q;_++)
{
int x,y;
cin>>x>>y;
memset(g,0,sizeof(g));
for(int i=1;i<=n;i++)
{
g[0][i]=(dp[x][i]*inv+dp[y][i]*inv)%mod;
g[1][i]=(dp[i][x]*inv+dp[i][y]*inv)%mod;
}
int dxy=(dp[x][y]*inv+dp[y][x]*inv)%mod;
for(int i=1;i<=n;i++) dp[x][i]=dp[y][i]=g[0][i],dp[i][x]=dp[i][y]=g[1][i];
dp[x][y]=dp[y][x]=dxy;
}
int ans=0;
for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) ans=(ans+dp[i][j])%mod;
cout<<ans*fpow(2,Q)%mod;
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【AGC017F】Zigzag
一个最显然的 dp:\(f(i,S)\) 表示考虑了前 \(i\) 条轮廓线,第 \(i\) 条轮廓线的形状是二进制串 \(S\)(\(S_j=0\) 表示第 \(j\) 步往左走,否则往右走),转移枚举另一条轮廓线 \(T\),复杂度是 \(O(4^npoly(n))\) 的,无法通过。
我们尝试去 dp 出每一条轮廓线 \(T\) 所有对应的合法 \(S\) 的 \(f(i,S)\) 之和,考虑什么样的一个 \(S\) 合法,即任何一个前缀,\(T\) 的 \(1\) 的个数均 \(\ge S\)。令 \(g(i,R)\) 表示填了 \(T\) 的前 \(i\) 位,之后的限制是 \(R\) 的 \(f(S)\) 之和,令即将填的位为 \(x\),\(R\) 对应的位为 \(y\),转移:
- 若 \(x=y\),直接将 \(g(i,R)\) 转移到 \(g(i,R+1)\)。
- 若 \(x=0,y=1\),转移不合法。
- 若 \(x=1,y=0\),此时需要找到一个新的 \(R'\),即将 \(R\) 某一位之后的一个 \(1\) 变成 \(0\),将 \(g(i,R)\) 转移到 \(g(i+1,R')\)。
预处理 \(R'\),复杂度 \(O(2^npoly(n))\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const int INF=1e18;
void add(int &x,int y)
{
x+=y;
if(x>=mod) x-=mod;
}
int lim[21][21];
int n,m,K;
int dp[20][1<<20],f[1<<20],nxt[1<<20][20];
void solve()
{
memset(lim,-1,sizeof(lim));
cin>>n>>m>>K;
n--;
while(K--)
{
int x,y,z;
cin>>x>>y>>z;
lim[x-1][y-1]=z;
}
for(int mask=0;mask<(1<<n);mask++) for(int i=0;i<n;i++)
{
int S=mask;
S|=(1<<i);
for(int j=i+1;j<n;j++) if((S&(1<<j)))
{
S^=(1<<j);
break;
}
nxt[mask][i]=S;
}
for(int mask=0;mask<(1<<n);mask++)
{
int ok=1;
for(int i=0;i<n;i++) if(lim[0][i]!=-1)
{
int d=0;
if(mask&(1<<i)) d=1;
if(d!=lim[0][i])
{
ok=0;
break;
}
}
// if(ok) cout<<mask<<"...\n";
f[mask]=ok;
}
for(int t=1;t<m;t++)
{
memset(dp,0,sizeof(dp));
for(int S=0;S<(1<<n);S++) dp[0][S]=f[S];
for(int i=0;i<n;i++) for(int S=0;S<(1<<n);S++) if(dp[i][S])
{
if(lim[t][i]==-1||lim[t][i]==0) // add 0
{
if(!(S&(1<<i))) add(dp[i+1][S],dp[i][S]);
}
if(lim[t][i]==-1||lim[t][i]==1) // add 1
{
if(!(S&(1<<i))) add(dp[i+1][nxt[S][i]],dp[i][S]);
else add(dp[i+1][S],dp[i][S]);
}
}
for(int S=0;S<(1<<n);S++) f[S]=dp[n][S];//,cout<<S<<" "<<f[S]<<"\n";
}
int ans=0;
for(int i=0;i<(1<<n);i++) add(ans,f[i]);
cout<<ans<<"\n";
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【AGC032D】Rotation Sort
一次操作可以看做把一个数字往左/往右移动到任意位置。
如果确定了一些数不去操作,那么就能确定每个数是往左移动还是往右移动,答案就可以计算了。
令 \(dp(i)\) 表示前 \(i\) 个数确定,第 \(i\) 个数不操作,的最小代价,转移枚举 \(j\),要求 \(p_i \lt p_j\),代价和 \([i+1,j-1]\) 中 \(\lt i, \gt j\) 的个数有关。
复杂度 \(O(N^2)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n,c1,c2;
int a[5005];
int dp[5005];
void solve()
{
cin>>n>>c1>>c2;
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(int i=1;i<=n;i++) cin>>a[i];
a[0]=0,a[n+1]=n+1;
for(int i=0;i<=n;i++)
{
int x=0,y=0;
for(int j=i+1;j<=n+1;j++)
{
if(a[i]<a[j]) dp[j]=min(dp[j],dp[i]+x*c1+y*c2);
if(a[j]>a[i]) x++;
else y++;
}
}
cout<<dp[n+1]<<"\n";
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【AGC036F】Square Constraints
每个位置的取值是一个区间。
如果区间没有下界,那么是 trivial 的,按上界从小到大排序就行。
考虑对超出下界的数容斥,枚举超出下界的数的个数,令 \(dp(i,j)\) 表示前 \(i\) 个数有 \(j\) 个超出下界的方案数,由于存在下界的数,的所有上界,均大于等于不存在下界的数的所有上界,所以上界的排名和转移系数都是很容易刻画的。
复杂度 \(O(n^3)\)。
Code
``` #include【ARC156E】Non-Adjacent Matching
观察出充要条件,令所有 \(X\) 的和为 \(S\),则 \(S\) 是偶数;且对于任意 \(i\),有 \(X_i+X_{(i+1)\bmod n} \le S/2\)。
先不考虑第二个条件,则答案可以写作 \(\sum_{k=0} [x^k]F^{n}(x) \cdot [k 是偶数]\),其中 \(F(x)=\sum_{i=0}^{M} x^i\)。
\(F(x)\) 通过等比数列求和可以写作 \(\frac{P(x)}{Q(x)}\) 的形式,其中 \(P(x)\) 可以二项式定理算,\(Q(x)\) 可以运用组合意义算。
考虑对第二个条件容斥,很容易观察到最多存在两个 \(i\) 不合法,若存在两个,则两个 \(i\) 一定相邻。
- 存在一个:令 \(X_1+X_2\) 不合法,此时枚举 \(X_1+X_2\),分奇偶维护 \(F^{n-2}(x)\) 每一项系数的前缀和。
- 存在两个:令 \(X_1+X_2,X_2+X_3\) 不合法,枚举 \(X_2\) 和 \(S\),则合法的 \(X_1,X_3\) 的限制形如,和属于一个区间,差属于一个区间,分奇偶维护二维前缀和就行。
复杂度 \(O(n^2)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int fpow(int x,int b)
{
if(x==0) return 0;
if(b==0) return 1;
int res=1;
while(b>0)
{
if(b&1) res=1LL*res*x%mod;
x=1LL*x*x%mod;
b>>=1;
}
return res;
}
int fac[20000005],ifac[20000005];
int C(int x,int y)
{
if(y>x) return 0;
if(x==y||y==0) return 1;
return 1LL*fac[x]*ifac[x-y]%mod*ifac[y]%mod;
}
void init(int x)
{
fac[0]=fac[1]=1;
for(int i=2;i<=x;i++) fac[i]=1LL*fac[i-1]*i%mod;
ifac[x]=fpow(fac[x],mod-2);
for(int i=x-1;i>=0;i--) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
int calc2(int x,int m,int k)
{
int res=0;
vector <pii > P;
vector <int> Q;
for(int i=0;i<=x;i++)
{
int coef=C(x,i);
if(i%2) coef=(mod-coef)%mod;
P.pb(mp(coef,i*(m+1)));
}
for(int i=0;i<=x*m;i++)
{
Q.pb(C(x-1+i,x-1));
if(i>=2) Q[i]=(Q[i]+Q[i-2])%mod;
}
// int res=0;
for(int i=0;i<P.size();i++)
{
int coef=P[i].fi,j=P[i].se;
int R=k-j;
if(R%2!=j%2) R--;
if(R>=0) res=(res+Q[R]*coef)%mod;
}
return res;
}
vector <int> calc(int n,int m)
{
m++,swap(n,m);
vector <int> R;
for(int k=0;k<=min(2LL*n,n*m);k++)
{
int ans=0;
for(int i=0;i<k/n+1;i++)
{
int j=k-n*i;
int coef=C(m+j-1,m-1)*C(m,i)%mod;
if(i&1) coef=(mod-coef);
ans=(ans+coef)%mod;
}
R.pb(ans);
}
return R;
}
int n,m,k;
int cnt2[2][10000005],cnt[2][6105][6105];
void solve()
{
init(20000000);
cin>>n>>m>>k;
int ans=calc2(n,m,k);
// for(int i=0;i<F.size();i++) cout<<F[i]<<" ";
// cout<<"\n";
// cout<<ans<<"\n";
// system("pause");
vector <int> F=calc(n-2,m);
for(int i=0;i<=m;i++) for(int j=0;j<=m;j++) if(k-i-j+1>=1) cnt2[(i+j)%2][0]++,cnt2[(i+j)%2][min(k-i-j+1,i+j)]--;
for(int i=1;i<=k;i++) cnt2[0][i]+=cnt2[0][i-1],cnt2[1][i]+=cnt2[1][i-1];
for(int i=0;i<=k;i++) if(i<F.size())
{
int coef=n*cnt2[i%2][i]%mod*F[i]%mod;
ans=(ans-coef+mod)%mod;
// cout<<i<<" "<<F[i]<<" "<<cnt2[i]<<"\n";
}
// cout<<ans<<"\n";
// cout<<ans<<"\n";
// memset(cnt2,0,sizeof(cnt2));
F=calc(n-3,m);
for(int d1=0;d1<=m;d1++) for(int d3=0;d3<=m;d3++)
{
cnt[(d1+d3)%2][abs(d1-d3)+1][d1+d3]++;
}
// cout<<ans<<"\n";
for(int op=0;op<2;op++) for(int i=0;i<=2*m;i++) for(int j=0;j<=2*m;j++)
cnt[op][i][j]+=(j?cnt[op][i][j-1]:0)+(i?cnt[op][i-1][j]:0)-((i&&j)?cnt[op][i-1][j-1]:0);
// for(int d2=0;d2<=m;d2++) for(int mx=0;mx<=m;mx++)
// {
// int L=0,R=mx-1;
// if(L<=R)
// {
// L+=D,R+=D;
// cnt2[1]+=2*(R-L+1),cnt2[2]-=2*(R-L+1);
// cnt2[L+1+d2+mx]-=2,cnt2[R+1+d2+mx]+=2;
// }
// cnt2[1]++,cnt2[2*(d2+mx+mx)+1]--;
// }
// for(int i=1;i<=2*D;i++) cnt2[i]+=cnt2[i-1];
// for(int i=0;i<=2*D;i++) cnt2[i]%=mod;
// for(int i=1;i<=2*D;i++) cnt2[i]+=cnt2[i-1];
// for(int i=0;i<=2*D;i++) cnt2[i]%=mod;
for(int d2=0;d2<=m;d2++) for(int S=0;S<=2*m;S++) if(S<F.size()&&d2-S>0)
{
if(k-d2-S<0) continue;
int coef=cnt[(d2+S)%2][d2-S][min(2LL*m,k-d2-S)];
coef=coef*F[S]%mod*n%mod;
ans=(ans+coef)%mod;
}
cout<<ans<<"\n";
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【ARC157F】XY Ladder LCS
一个很直观的想法,答案串不会很短,即不会存在太长的一段没有被选到 LCS 里面,令阈值为 \(B\)。
令 \(dp(i,R)\) 表示考虑了前 \(i\) 位,\(S\) 的末尾是 \(R\),的最长 LCS。
此时需要记录 \(R\) 的长度,再开一维可能无法接受,但是我们可以用 \(R\) 的最高位去表示长度,也可以用类似的方法去表示 \(dp(i,R)\),这样比较字典序就成了比较整数,非常方便。
可能需要预处理一些二进制的东西。
复杂度 \(O(2^Bpoly(N))\),\(B=20\) 稳过,\(B=13\) 也能过(?)但我也不会卡。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
const int B=20;
int dp[2][1<<(B+1)];
int n;
char S[55],T[55];
int to[2][1<<(B+1)],hb[1<<(B+1)];
void solve()
{
cin>>n;
cin>>(S+1)>>(T+1);
for(int i=0;i<(1<<(B+1));i++)
{
to[0][i]=to[1][i]=-1;
bool ok=0;
for(int j=B;j>=0;j--)
{
if(i&(1<<j))
{
if(!ok)
{
ok=1,hb[i]=j;
continue;
}
if(ok&&to[1][i]==-1) to[1][i]=(i&((1<<j)-1))|(1<<j);
}
else if(ok&&to[0][i]==-1) to[0][i]=(i&((1<<j)-1))|(1<<j);
}
}
memset(dp,0,sizeof(dp));
dp[0][1]=1;
int nw=0;
for(int i=1;i<=n;i++)
{
nw^=1;
memset(dp[nw],0,sizeof(dp[nw]));
int u=(S[i]-'X')^1,v=(T[i]-'X')^1;
for(int mask=0;mask<(1<<(B+1));mask++) if(dp[nw^1][mask])
{
// cout<<i-1<<" "<<mask<<": "<<dp[nw^1][mask]<<"\n";
// giveup one
if(hb[mask]<B)
{
dp[nw][mask<<1|u]=max(dp[nw][mask<<1|u],dp[nw^1][mask]);
dp[nw][mask<<1|v]=max(dp[nw][mask<<1|v],dp[nw^1][mask]);
}
// match u
if(to[u][mask]!=-1) dp[nw][to[u][mask]<<1|v]=max(dp[nw][to[u][mask]<<1|v],dp[nw^1][mask]<<1LL|u);
// match v
swap(u,v);
if(to[u][mask]!=-1) dp[nw][to[u][mask]<<1|v]=max(dp[nw][to[u][mask]<<1|v],dp[nw^1][mask]<<1LL|u);
if(u==v) dp[nw][1]=max(dp[nw][1],dp[nw^1][mask]<<1LL|v);
}
}
int ans=1;
for(int mask=0;mask<(1<<(B+1));mask++) ans=max(ans,dp[nw][mask]);
bool ok=0;
// cout<<ans<<"\n";
for(int i=60;i>=0;i--)
{
if(ans&(1LL<<i))
{
if(!ok) ok=1;
else cout<<"X";
}
else if(ok) cout<<"Y";
}
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【JOISC 2019】Two Transportations
考虑 Dijkstra 的过程,每次找到一个距离最小的点去松弛,如何知道最小的点?我们可以令 A 和 B 把最小的距离发给对面,如果发现自己的距离小,就将对应点的编号发给对面,这样双方都知道最小的点了。
还有一个问题是,这个距离可能会达到 \(O(NW)\) 级别,\(W\) 是边权范围。令 \(M\) 为目前已知点中距离的最大值,那么距离一定在 \([M,M+W]\) 之间,传递的时候减去 \(M\) 再发送,一轮松弛传递的比特数量为 \(2 \log W+\log n=29\),总共不超过 \(29n=58000\) 比特,可以通过。
Code
#include "Azer.h"
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int inf=0x3f3f3f3f;
struct Alice
{
vector <pii > g[2005];
bool vis[2005];
int dist[2005],n;
pii getu()
{
pii res=mp(2*inf+1,-1);
int maxx=-inf;
for(int i=1;i<=n;i++) if(vis[i]) maxx=max(maxx,dist[i]);
for(int i=1;i<=n;i++) if(!vis[i]) res=min(res,mp(dist[i]-maxx,i));
return res;
}
void reportnum(int x,int lg)
{
if(lg==9) x=min(x,511);
for(int i=lg-1;i>=0;i--)
{
if(x&(1<<i)) SendA(1);
else SendA(0);
}
}
void relax(int u)
{
vis[u]=1;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i].fi,w=g[u][i].se;
dist[v]=min(dist[v],dist[u]+w);
}
pii x=getu();
if(x.se!=-1) reportnum(x.fi,9);
}
int cnt=0,nw=0,tmp_len=0;
bool Flg=0;
}A;
void ReceiveA(bool x)
{
A.cnt++;
A.nw*=2;
if(x) A.nw++;
if(A.cnt==9&&A.Flg==0)
{
pii t=A.getu();
if(t.fi==511&&A.nw==511) return;
if(t.fi<=A.nw)
{
A.reportnum(t.se,11);
A.relax(t.se);
}
else A.Flg=1;
A.tmp_len=A.nw;
A.cnt=A.nw=0;
}
if(A.cnt==11&&A.Flg==1)
{
pii t=A.getu();
int mx=A.dist[t.se]-t.fi;
A.tmp_len+=mx,A.dist[A.nw]=A.tmp_len;
A.relax(A.nw);
A.Flg=0;
A.cnt=A.nw=0;
}
}
void InitA(int N,int M,vector <int> U,vector <int> V,vector <int> C)
{
A.n=N;
memset(A.vis,0,sizeof(A.vis)),memset(A.dist,0x3f,sizeof(A.dist)),A.dist[1]=0;
for(int i=0;i<M;i++) A.g[U[i]+1].pb(mp(V[i]+1,C[i])),A.g[V[i]+1].pb(mp(U[i]+1,C[i]));
A.relax(1);
}
vector <int> Answer()
{
vector <int> vec;
for(int i=1;i<=A.n;i++) vec.pb(A.dist[i]);
return vec;
}
#include "Baijan.h"
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int inf=0x3f3f3f3f;
struct Bob
{
vector <pii > g[2005];
bool vis[2005];
int dist[2005],n;
pii getu()
{
pii res=mp(2*inf+1,-1);
int maxx=-inf;
for(int i=1;i<=n;i++) if(vis[i]) maxx=max(maxx,dist[i]);
for(int i=1;i<=n;i++) if(!vis[i]) res=min(res,mp(dist[i]-maxx,i));
return res;
}
void reportnum(int x,int lg)
{
if(lg==9) x=min(x,511);
for(int i=lg-1;i>=0;i--)
{
if(x&(1<<i)) SendB(1);
else SendB(0);
}
}
void relax(int u)
{
vis[u]=1;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i].fi,w=g[u][i].se;
dist[v]=min(dist[v],dist[u]+w);
}
pii x=getu();
reportnum(x.fi,9);
}
int cnt=0,nw=0,tmp_len;
bool Flg=0;
}B;
void ReceiveB(bool x)
{
B.cnt++;
B.nw*=2;
if(x) B.nw++;
if(B.cnt==9&&B.Flg==0)
{
pii t=B.getu();
if(t.fi==511&&B.nw==511) return;
if(t.fi<B.nw)
{
B.reportnum(t.se,11);
B.relax(t.se);
}
else B.Flg=1;
B.tmp_len=B.nw;
B.cnt=B.nw=0;
}
if(B.cnt==11&&B.Flg==1)
{
pii t=B.getu();
int mx=B.dist[t.se]-t.fi;
B.tmp_len+=mx,B.dist[B.nw]=B.tmp_len;
B.relax(B.nw);
B.Flg=0;
B.cnt=B.nw=0;
}
}
void InitB(int N,int M,vector <int> U,vector <int> V,vector <int> C)
{
B.n=N;
memset(B.vis,0,sizeof(B.vis)),memset(B.dist,0x3f,sizeof(B.dist)),B.dist[1]=0;
for(int i=0;i<M;i++) B.g[U[i]+1].pb(mp(V[i]+1,C[i])),B.g[V[i]+1].pb(mp(U[i]+1,C[i]));
B.relax(1);
}
【JOISC 2019】馕
如果确定了排列 \(P\),问题是容易的,对每个人二分出一个最短的合法前缀就行。
我们想构造出一个尽可能优秀的排列 \(P\),令 \(f(i,j)\) 表示第 \(i\) 个人,获得的快乐度不小于 \(\frac{\sum_k V_{i,k}}{j}\) 的一个最小前缀,从前往后构造排列 \(P\),令目前构造的位置为 \(P_x\),则选择 \(f(i,x)\) 最小的且没被选过的 \(i\) 作为 \(P_x\),可以归纳证明,此时选择的 \(i\) 一定合法。
复杂度 \(O(N^2 \log N)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
struct Frac
{
__int128 p,q;
};
Frac frac(int p,int q)
{
Frac res;
res.p=p,res.q=q;
return res;
}
void qwq(Frac &x)
{
__int128 g=__gcd(x.p,x.q);
x.p/=g,x.q/=g;
}
Frac operator +(Frac a,Frac b)
{
__int128 Q=a.q*b.q/__gcd(a.q,b.q);
a.p*=Q/a.q,b.p*=Q/b.q;
Frac res;
res.p=a.p+b.p,res.q=Q;
qwq(res);
return res;
}
Frac operator -(Frac a,Frac b)
{
b.p*=-1;
return a+b;
}
Frac operator *(Frac a,Frac b)
{
Frac res;
res.p=a.p*b.p,res.q=a.q*b.q;
qwq(res);
return res;
}
Frac operator /(Frac a,Frac b)
{
swap(b.p,b.q);
if(b.q<0) b.p*=-1,b.q*=-1;
return a*b;
}
bool operator >(Frac a,Frac b)
{
int Q=a.q*b.q/__gcd(a.q,b.q);
a.p*=Q/a.q,b.p*=Q/b.q;
return a.p>b.p;
}
bool operator >=(Frac a,Frac b)
{
int Q=a.q*b.q/__gcd(a.q,b.q);
a.p*=Q/a.q,b.p*=Q/b.q;
return a.p>=b.p;
}
const int D=1e9;
void qaq(Frac &tmp)
{
if(tmp.q>D)
{
__int128 p2=tmp.p*(__int128)(D);
p2=(p2)/tmp.q;
tmp.p=p2,tmp.q=(__int128)(D);
}
}
int n,m;
int V[2005][2005],sum[2005][2005];
Frac lst;
Frac lim[2005];
int P[2005],vis[2005];
Frac chk(int u)
{
int st=lst.p/lst.q;
st++;
Frac len=frac(st,1)-lst;
if((len*frac(V[u][st],1))>=lim[u]) return lim[u]/frac(V[u][st],1);
Frac l=lim[u]-(len*frac(V[u][st],1));
int L=st+1,R=m,res=st;
// cerr<<u<<" "<<L<<" "<<R<<" "<<res<<"\n";
while(L<=R)
{
int mid=(L+R)>>1;
if(l>=frac(sum[u][mid]-sum[u][st],1)) res=mid,L=mid+1;
else R=mid-1;
}
Frac ans=frac(res,1);
// cout<<lim[u].p<<" "<<lim[u].q<<"\n";
Frac tmp=len*frac(V[u][st],1);
// cout<<tmp.p<<" "<<tmp.q<<"\n";
// cout<<l.p<<" "<<l.q<<"\n";
l=l-frac(sum[u][res]-sum[u][st],1);
ans=ans+(l/frac(V[u][res+1],1));
// cout<<"... "<<st<<" "<<res<<" "<<ans.p<<" "<<ans.q<<"\n";
return ans-lst;
}
Frac res[2005][2005];
void solve()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int s=0;
for(int j=1;j<=m;j++) cin>>V[i][j],s+=V[i][j],sum[i][j]=s;
V[i][m+1]=1;
lim[i]=frac(s,n),qwq(lim[i]);
}
for(int i=1;i<=n;i++)
{
lst=frac(0,1);
for(int j=1;j<=n;j++)
{
Frac tmp=chk(i);
lst=lst+tmp;
res[j][i]=lst;
}
}
lst=frac(0,1);
for(int i=1;i<=n;i++)
{
int u=-1;
Frac X=frac(inf,1);
for(int j=1;j<=n;j++) if(!vis[j])
{
Frac tmp=res[i][j];
if(X>tmp) X=tmp,u=j;
}
vis[u]=1,P[i]=u;
lst=X;
if(i<n) cout<<(int)lst.p<<" "<<(int)lst.q<<"\n";
}
for(int i=1;i<=n;i++) cout<<P[i]<<" ";
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【2023 HDU 多校】Werewolves
如果说知道了所有卡牌的和,那么在模 \(m\) 意义下,用和减去所有看到的卡牌,就能知道自己的卡牌。
问题是你不知道卡牌的和, 把序列平均分成 \(m\) 块,第 \(i\) 块假装和在模 \(m\) 意义下为 \(i\),则至少有一块能猜对,符合 \(\lfloor n/m \rfloor\) 的限制。
Code
//#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
#define N 200005
#define mod 998244353
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define ld long double
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define SZ(x) (int)(x.size())
#define debug cout<<endl<<"ff"<<endl
#define YES cout<<"YES"<<endl
#define NO cout<<"NO"<<endl
#define fi first
#define se second
#define INF 1e9
#define pq priority_queue
#define rep(x,a,b) for(int x=a;x<=b;x++)
int qpow(int a,int b){
int res=1;
for(;b;b>>=1){
if(b&1) res=res*a%mod;
a=a*a%mod;
}
return res;
}
/*int fac[N],ifac[N];
int C(int n,int m){
if(m>n||m<0||n<0) return 0;
return fac[n]*ifac[n-m]%mod*ifac[m]%mod;
}
void init(){
fac[0]=1;
for(int i=1;i<N;i++) fac[i]=fac[i-1]*i%mod;
ifac[N-1]=qpow(fac[N-1],mod-2);
for(int i=N-2;i>=0;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
}*/
/*struct node{
int nxt,to;
}e[N<<1];
int cnt=1,head[N];
inline void add(int x,int y){
e[++cnt].nxt=head[x];
head[x]=cnt;
e[cnt].to=y;
}*/
inline int lowbit(int x){return x&(-x);}
inline int read(){
int x=0,t=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') t=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch-'0');
ch=getchar();
}
return x*t;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x>=10) write(x/10);
putchar(x%10+'0');
}
int T,n,m;
vector<int>v[100];
void dfs(int x,int lst,int sum){
if(x==n){
for(int i=1;i<=n;i++){
int tmp=i%m;
v[i].pb(((tmp-sum)%m+m)%m);
}
return;
}
for(int k=lst;k<m;k++) dfs(x+1,k,(sum+k)%m);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>n>>m;
for(int i=1;i<=n;i++) v[i].clear();
dfs(1,0,0);
for(int i=1;i<=n;i++){
for(auto t:v[i]) cout<<t+1<<" ";
cout<<'\n';
}
}
return 0;
}
【2023 HDU 多校】Broken Dream
按照 Kruskal 的过程,将边按 \(w_i\) 从小到大排序,令 \(f(i,S),g(i,S)\) 分别表示在加入前 \(i\) 条边的情况下,点集 \(S\) 是一个极大连通块的概率和期望。加入边 \((u,v,w,p)\) 的时候,枚举一个包含 \(u\) 但不包含 \(v\) 的集合 \(S\),和一个包含 \(v\) 但不包含 \(u\) 的集合 \(T\),转移到 \(S|T\)。
此时有一个问题,如果之前有一条边,一端在 \(S\) 里一端在 \(T\) 里,我们想让这条边不出现,但这条边带来的 \((1-p)\) 的贡献被算了两次,我们要把它除掉。如果能快速维护出每一对 \(S,T\) 之间的连边,问题就解决了。
考虑维护 \(E(R)\) 表示有一端在集合 \(R\) 里面的边的集合,我们需要支持快速查询 \(E(S)\&E(T)\) 里面所有边的 \((1-p)\) 的积。用四毛子,将边每 \(B\) 个分成一块,每一块预处理 \(2^B\) 种可能带来的贡献。
复杂度 \(O(3^nm^2/B+2^BB)\),取 \(B=m/3\) 可以通过。
Code
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#include<bits/stdc++.h>
using namespace __gnu_pbds;
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int fpow(int x,int b)
{
if(x==0) return 0;
if(b==0) return 1;
int res=1;
while(b>0)
{
if(b&1) res=1LL*res*x%mod;
x=1LL*x*x%mod;
b>>=1;
}
return res;
}
int n,m;
array<int,4> es[55];
int f[55][1<<15],g[55][1<<15],inv[55];
int ma1[1<<15],ma2[1<<15],ma3[1<<15],sum1[1<<20],sum2[1<<20],sum3[1<<20];
void solve()
{
cin>>n>>m;
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
for(int i=1;i<=m;i++) cin>>es[i][1]>>es[i][2]>>es[i][0]>>es[i][3],es[i][1]--,es[i][2]--;
sort(es+1,es+1+m);
for(int i=0;i<m;i++) inv[i]=fpow((mod+1-es[i+1][3])%mod,mod-2);
for(int i=0;i<n;i++) g[0][(1<<i)]=1;
for(int i=0;i<(1<<n);i++) ma1[i]=ma2[i]=ma3[i]=0;
int B1=m/3,B2=m/3*2;
for(int i=0;i<(1<<B1);i++)
{
sum1[i]=1;
for(int j=0;j<B1;j++) if(i&(1<<j)) sum1[i]=sum1[i]*inv[j]%mod;
}
for(int i=0;i<(1<<(B2-B1));i++)
{
sum2[i]=1;
for(int j=0;j<B2-B1;j++) if(i&(1<<j)) sum2[i]=sum2[i]*inv[j+B1]%mod;
}
for(int i=0;i<(1<<(m-B2));i++)
{
sum3[i]=1;
for(int j=0;j<m-B2;j++) if(i&(1<<j)) sum3[i]=sum3[i]*inv[j+B2]%mod;
}
for(int i=0;i<m;i++)
{
int u=es[i+1][1],v=es[i+1][2],w=es[i+1][0],p=es[i+1][3];
// cout<<"edge: "<<u<<" "<<v<<" "<<w<<" "<<p<<"\n";
// for(int mask=0;mask<(1<<n);mask++) cout<<i<<" "<<mask<<" "<<g[i][mask]<<" "<<f[i][mask]<<"\n";
// system("pause");
for(int mask=0;mask<(1<<n);mask++) if(g[i][mask])
{
if(mask&(1<<u)) swap(u,v);
if((!(mask&(1<<v)))||((mask&(1<<u))))
{
g[i+1][mask]=(g[i+1][mask]+g[i][mask])%mod;
f[i+1][mask]=(f[i+1][mask]+f[i][mask])%mod;
continue;
}
g[i+1][mask]=(g[i+1][mask]+g[i][mask]*(mod+1-p))%mod;
f[i+1][mask]=(f[i+1][mask]+f[i][mask]*(mod+1-p))%mod;
}
for(int S=0;S<(1<<n);S++) if((S&(1<<u))&&(!(S&(1<<v)))&&g[i][S])
{
int tmp=((1<<n)-1)^S^(1<<v);
for(int mask=tmp;;mask=(mask-1)&tmp)
{
int T=((1<<n)-1)^S^mask;
bool Flg=(S>T);
if(Flg) swap(S,T);
int prob=sum1[ma1[S]&ma1[T]]*sum2[ma2[S]&ma2[T]]%mod*sum3[ma3[S]&ma3[T]]%mod;
// cout<<i<<" "<<S<<" "<<T<<" "<<prob<<"\n";
g[i+1][S|T]=(g[i+1][S|T]+g[i][S]*g[i][T]%mod*prob%mod*p)%mod;
f[i+1][S|T]=(f[i+1][S|T]+f[i][S]*g[i][T]%mod*p%mod*prob%mod
+f[i][T]*g[i][S]%mod*p%mod*prob%mod
+g[i][S]*g[i][T]%mod*prob%mod*p%mod*w%mod)%mod;
// cout<<"trans: "<<S<<" "<<T<<" "<<(S|T)<<" "<<f[i][S]<<" "<<f[i][T]<<" "<<g[i][S]<<" "<<g[i][T]<<" "<<((f[i][S]+f[i][T])*p+g[i][S]*g[i][T]%mod*p%mod*w%mod)%mod<<"\n";
if(Flg) swap(S,T);
if(!mask) break;
}
}
for(int S=0;S<(1<<n);S++) if((S&(1<<u))||((S&(1<<v))))
{
if(i<B1) ma1[S]|=(1<<i);
else if(i<B2) ma2[S]|=(1<<(i-B1));
else ma3[S]|=(1<<(i-B2));
}
}
cout<<f[m][(1<<n)-1]<<"\n";
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
cin>>_;
while(_--) solve();
return 0;
}
【2023 牛客多校】Journey
先考虑预留出一条从起点到终点的路径,然后问题就变成了,需要从起点出发,然后回到起点,每个点有经过次数奇偶性的限制。
进行一次 dfs,如果发现一个点的子树内都走完了,但自己颜色不对,则走到 dfs 树的父亲,然后走回来,然后再走到 dfs 树的父亲,就能将自己反色。可以归纳证明只要需要改变的点的奇偶性正确,则一定能像这样找出解。
有一个问题,预留出一条路径,由于需要考虑长度分别为奇数偶数路径,长度能达到 \(2n\),走 dfs 树需要 \(2n\) 的步数,最多进行 \(n\) 次调整,一次 \(2\) 步,步数是 \(6n\)。
观察到进行调整的次数和某种颜色的出现次数有关,如果发现次数超了,将所有点反色再做一遍,次数就是对的了。
复杂度 \(O(n)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,m;
int col[100005],a[100005],vis[100005],ok[100005][2],tar[100005];
pii prv[100005][2];
vector <int> g[100005],ans;
void dfs(int u,int par)
{
vis[u]=1;
ans.pb(u),tar[u]^=1;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(vis[v]) continue;
dfs(v,u);
ans.pb(u),tar[u]^=1;
}
if(tar[u])
{
// assert(u!=1);
ans.pb(par),tar[par]^=1,ans.pb(u),tar[u]^=1;
}
}
void work()
{
ans.clear();
memset(vis,0,sizeof(vis));
memset(prv,-1,sizeof(prv));
memset(ok,0,sizeof(ok));
queue <pii > q;
q.push(mp(1,0)),ok[1][0]=1;
for(int i=1;i<=n;i++) a[i]=col[i];
while(q.size())
{
int u=q.front().fi,d=q.front().se;
q.pop();
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i],d2=(d^1);
if(!ok[v][d2]) ok[v][d2]=1,prv[v][d2]=mp(u,d),q.push(mp(v,d2));
}
}
if(ok[n][0])
{
int u=n,d=0;
memset(tar,0,sizeof(tar));
vector <int> pa;
while(u!=-1)
{
pii t=prv[u][d];
tar[u]^=1,pa.pb(u),u=t.fi,d=t.se;
}
tar[1]^=1,pa.pop_back();
reverse(pa.begin(),pa.end());
int cnt=0;
for(int i=1;i<=n;i++) tar[i]^=a[i],cnt+=tar[i];
tar[1]^=1;
if(cnt%2==0)
{
dfs(1,-1);
for(int j=0;j<pa.size();j++) ans.pb(pa[j]);
if(ans.size()<=500000)
{
cout<<"YES\n";
cout<<ans.size()-1<<"\n";
for(int j=0;j<ans.size();j++) cout<<ans[j]<<" ";
exit(0);
}
}
}
if(ok[n][1])
{
int u=n,d=1;
memset(tar,0,sizeof(tar));
vector <int> pa;
while(u!=-1)
{
pii t=prv[u][d];
tar[u]^=1,pa.pb(u),u=t.fi,d=t.se;
}
tar[1]^=1,pa.pop_back();
reverse(pa.begin(),pa.end());
int cnt=0;
for(int i=1;i<=n;i++) tar[i]^=a[i],cnt+=tar[i];
tar[1]^=1;
if(cnt%2==0)
{
dfs(1,-1);
for(int j=0;j<pa.size();j++) ans.pb(pa[j]);
if(ans.size()<=500000)
{
cout<<"YES\n";
cout<<ans.size()-1<<"\n";
for(int j=0;j<ans.size();j++) cout<<ans[j]<<" ";
exit(0);
}
}
}
}
void solve()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],col[i]=a[i];
while(m--)
{
int u,v;
cin>>u>>v;
g[u].pb(v),g[v].pb(u);
}
work();
for(int i=1;i<=n;i++) col[i]^=1;
work();
cout<<"NO\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【2023 牛客多校】Link with Checksum
先假设答案全部为 \(0\)。
考虑将一个位置取反,可以通过打表验证,无论剩余部分是什么,对于加密结果的影响是确定的,都是把一些位置异或 \(1\)。
然后就可以写出 \(32\) 个未知数 \(32\) 个方程,高斯消元求解就行。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int xorpos[21],ans[35],L;
int n,m,a[35][35];
int P=0x04C11DB7;
int mask[2000015];
int enc(vector <int> vec)
{
for(int i=0;i<vec.size();i++) mask[i]=vec[i];
int len=vec.size();
for(int i=len;i<len+32;i++) mask[i]=0;
for(int i=0;i<len;i++) if(mask[i])
{
for(int j=0;j<L;j++) mask[i+xorpos[j]+1]^=1;
// cout<<i<<"\n";
// for(int j=0;j<len+32;j++)
// {
// cout<<mask[j];
// if(j%8==7) cout<<" ";
// }
// system("pause");
}
int res=0;
for(int i=len;i<len+32;i++) res*=2,res+=mask[i];
// cout<<len<<"\n";
// for(int i=0;i<len+32;i++)
// {
// cout<<mask[i];
// if(i%8==7) cout<<" ";
// }
// cout<<"\n";
return res;
}
int enc2(int len)
{
vector <int> vec;
vec.pb(1);
for(int i=1;i<len;i++) vec.pb(0);
return enc(vec);
}
int con[35];
void solve()
{
for(int i=0;i<32;i++) if(P&(1LL<<i)) xorpos[L++]=31-i;
cin>>n>>m;
vector <int> vec;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
for(int j=7;j>=0;j--)
{
if(x&(1<<j)) vec.pb(1);
else vec.pb(0);
}
}
for(int i=1;i<=32;i++) vec.pb(0);
for(int i=1;i<=m;i++)
{
int x;
cin>>x;
for(int j=7;j>=0;j--)
{
if(x&(1<<j)) vec.pb(1);
else vec.pb(0);
}
}
int nowans=enc(vec);
// cout<<nowans<<"\n";
// system("pause");
for(int i=31;i>=0;i--) con[i]=enc2(8LL*m+i+1);//,cout<<con[i]<<"\n";
n=32;
// cout<<nowans<<"\n";
for(int i=1;i<=32;i++)
{
a[i][i]^=1;
for(int j=1;j<=32;j++) if(con[j-1]&(1LL<<(i-1))) a[i][j]^=1;
if(nowans&(1LL<<(i-1))) a[i][n+1]=1;
else a[i][n+1]=0;
}
for(int i=1,pos=0;i<=n;i++)
{
int idx=0;
for(int j=i;j<=n;j++) if(a[j][i])
{
idx=j;
break;
}
if(!idx) continue;
pos++;
swap(a[pos],a[idx]);
for(int j=pos+1;j<=n;j++) if(a[j][i])
{
for(int l=i;l<=n+1;l++) a[j][l]^=a[pos][l];
}
}
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=n+1;j++) cout<<a[i][j]<<" ";
// cout<<"\n";
// }
for(int i=n;i>=1;i--)
{
int u=-1;
for(int j=1;j<=n;j++) if(a[i][j])
{
u=j;
break;
}
if(u==-1&&a[i][n+1])
{
cout<<"-1\n";
return;
}
ans[u]=a[i][n+1];
for(int j=n;j>u;j--) if(a[i][j]) ans[u]^=ans[j];
}
int res=0;
for(int i=1;i<=32;i++) if(ans[i]) res+=(1LL<<(i-1));
cout<<res<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【2023 牛客多校】Graph
先强化一下条件,将欧拉路径改成回路,而欧拉回路是一定有解的。
如果一个点在图中的度数为偶数,无论这个点是 \(0\) 还是 \(1\),其邻居中一定有偶数个 \(0\) 和偶数个 \(1\)。
如果一个点在图中的度数为奇数,若这个点是 \(0\),其邻居中一定有偶数个 \(0\),即有奇数个 \(1\);若这个点是 \(1\),其邻居中一定有偶数个 \(1\),可以发现上面两个条件可以写成:这个点和它的所有邻居中有奇数个 \(1\)。
\(n\) 个方程 \(n\) 个未知数,可以解了。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int deg[105];
int g[105][105],a[105][105],ans[105];
int n,m;
void solve()
{
cin>>n>>m;
while(m--)
{
int u,v;
cin>>u>>v;
deg[u]^=1,deg[v]^=1;
g[u][v]^=1,g[v][u]^=1;
}
for(int i=1;i<=n;i++)
{
if(deg[i]==0)
{
for(int j=1;j<=n;j++) a[i][j]=g[i][j];
a[i][n+1]=0;
}
else
{
for(int j=1;j<=n;j++) a[i][j]=g[i][j];
a[i][i]=1;
a[i][n+1]=1;
}
}
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=n+1;j++) cout<<a[i][j]<<" ";
// cout<<"\n";
// }
for(int i=1,pos=0;i<=n;i++)
{
int idx=0;
for(int j=i;j<=n;j++) if(a[j][i])
{
idx=j;
break;
}
if(!idx) continue;
pos++;
swap(a[pos],a[idx]);
for(int j=pos+1;j<=n;j++) if(a[j][i])
{
for(int l=i;l<=n+1;l++) a[j][l]^=a[pos][l];
}
}
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=n+1;j++) cout<<a[i][j]<<" ";
// cout<<"\n";
// }
for(int i=n;i>=1;i--)
{
int u=-1;
for(int j=1;j<=n;j++) if(a[i][j])
{
u=j;
break;
}
if(u==-1&&a[i][n+1])
{
cout<<"-1\n";
return;
}
ans[u]=a[i][n+1];
for(int j=n;j>u;j--) if(a[i][j]) ans[u]^=ans[j];
}
for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【CF1864G】Magic Square
由于每行每列只能被转一次,且每个数只能被转两次,所以转一行后,这一行所有数的列都是正确的(否则某个数至少会被转三次)。列同理。
对于题目中的第三个限制,可以理解为,如果同时存在行和列操作,则任意两行的移动距离不能相等,列同理。
称一行合法,当且仅当存在一个移动距离,移动后每一个数的列正确。手玩发现如果同时存在合法行和合法列,则无解。否则令 \(cnt\) 表示合法行/列的数量,则会有 \(cnt!\) 种方案,因为每一行的操作顺序是无关的。
模拟这 \(cnt\) 次操作,然后重复找合法行/列并模拟。
由于一轮至少令一行/列合法,复杂度 \(O(n) \times O(n^2)=O(n^3)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n;
int a[505][505],b[505][505];
int R[250005],C[250005];
int d1[505],d2[505],t1[505],t2[505],cnt1,cnt2,sme;
void get_ok()
{
memset(d1,-1,sizeof(d1));
memset(d2,-1,sizeof(d2));
cnt1=cnt2=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(j==0) d1[i]=(C[a[i][j]]-j+n)%n;
else if(d1[i]!=-1&&(C[a[i][j]]-j+n)%n!=d1[i]) d1[i]=-1;
}
if(d1[i]==0) d1[i]=-1;
if(d1[i]!=-1) cnt1++;
}
for(int j=0;j<n;j++)
{
for(int i=0;i<n;i++)
{
if(i==0) d2[j]=(R[a[i][j]]-i+n)%n;
else if(d2[j]!=-1&&(R[a[i][j]]-i+n)%n!=d2[j]) d2[j]=-1;
}
if(d2[j]==0) d2[j]=-1;
if(d2[j]!=-1) cnt2++;
}
for(int i=0;i<n;i++) if(d1[i]!=-1) t1[i]=d1[i];
for(int i=0;i<n;i++) if(d2[i]!=-1) t2[i]=d2[i];
}
void Rshift(int i,int d)
{
vector <int> vec;
for(int j=0;j<n;j++) vec.pb(a[i][j]);
for(int j=0;j<n;j++) a[i][j]=vec[(j-d+n)%n];
}
void Cshift(int j,int d)
{
vector <int> vec;
for(int i=0;i<n;i++) vec.pb(a[i][j]);
for(int i=0;i<n;i++) a[i][j]=vec[(i-d+n)%n];
}
bool equ()
{
for(int i=0;i<n;i++) for(int j=0;j<n;j++) if(a[i][j]!=b[i][j]) return 0;
return 1;
}
void solve()
{
cin>>n;
for(int i=0;i<n;i++) for(int j=0;j<n;j++) cin>>a[i][j];
for(int i=0;i<n;i++) for(int j=0;j<n;j++) cin>>b[i][j],R[b[i][j]]=i,C[b[i][j]]=j;
sme=0;
int sr=0,sc=0;
memset(t1,-1,sizeof(t1));
memset(t2,-1,sizeof(t2));
int ans=1;
while(1)
{
get_ok();
if(cnt1&&cnt2)
{
cout<<"0\n";
return;
}
sr+=cnt1,sc+=cnt2;
if(cnt1)
{
for(int i=1;i<=cnt1;i++) ans=ans*i%mod;
sme=0;
for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(t1[i]==t1[j]&&t1[i]!=-1) sme=1;
for(int i=0;i<n;i++) if(d1[i]!=-1) Rshift(i,d1[i]);
if(sme)
{
if(equ()&&sc==0) cout<<ans<<"\n";
else cout<<"0\n";
return;
}
get_ok();
sr+=cnt1,sc+=cnt2;
for(int i=1;i<=cnt2;i++) ans=ans*i%mod;
sme=0;
for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(t2[i]==t2[j]&&t2[i]!=-1) sme=1;
for(int i=0;i<n;i++) if(d2[i]!=-1) Cshift(i,d2[i]);
if(sme)
{
cout<<"0\n";
return;
}
else if(equ())
{
cout<<ans<<"\n";
return;
}
}
else if(cnt2)
{
for(int i=1;i<=cnt2;i++) ans=ans*i%mod;
sme=0;
for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(t2[i]==t2[j]&&t2[i]!=-1) sme=1;
for(int i=0;i<n;i++) if(d2[i]!=-1) Cshift(i,d2[i]);
if(sme)
{
if(equ()&&sr==0) cout<<ans<<"\n";
else cout<<"0\n";
return;
}
get_ok();
sr+=cnt1,sc+=cnt2;
for(int i=1;i<=cnt1;i++) ans=ans*i%mod;
sme=0;
for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(t1[i]==t1[j]&&t1[i]!=-1) sme=1;
for(int i=0;i<n;i++) if(d1[i]!=-1) Rshift(i,d1[i]);
if(sme)
{
cout<<"0\n";
return;
}
else if(equ())
{
cout<<ans<<"\n";
return;
}
}
else
{
if(equ()) cout<<ans<<"\n";
else cout<<"0\n";
return;
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
cin>>_;
while(_--) solve();
return 0;
}
【CEOI 2023】light
假设我们想获得满分,每次令 \(t=p\) 不劣,然后按编号从大到小的顺序决定保留哪些位置。假设目前保留了倒数第 \(x\) 个位置,那么一定需要保留一个在倒数第 \([x+1,2x+1]\) 的位置,否则若下一次删除 \(x\) 个位置,则无法满足条件。
每次找一个最大的在倒数第 \(2x+1\) 个位置及其后面的数,就能保留尽可能少的数。
然后,就过了,证明不会。
Code
#include "light.h"
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
vector <pii > vec;
int calc(int x)
{
int p=upper_bound(vec.begin(),vec.end(),mp(x+1,-1LL))-vec.begin();
if(vec[p-1].fi<=x&&x<=vec[p-1].se) return x;
return vec[p].fi;
}
int N=1;
void prepare()
{
}
pair <int,vector<int> > join(int pa)
{
if(!vec.size()) vec.pb(mp(1,1));
N+=pa;
for(int i=0;i<vec.size();i++) vec[i].se=vec[i].fi+pa;
vector <pii > tmp;
for(int i=0;i<vec.size();i++)
{
int j=i;
while(j+1<vec.size()&&vec[j+1].fi<=vec[j].se+1) j++;
tmp.pb(mp(vec[i].fi,vec[j].se));
i=j;
}
vec=tmp;
int pos=N;
vector <int> res;
while(1)
{
res.pb(pos);
if(pos==1) break;
pos-=N-pos+2;
if(pos<=1)
{
res.pb(1);
break;
}
pos=calc(pos);
continue;
}
reverse(res.begin(),res.end());
vec.clear();
for(int i=0;i<res.size();i++) vec.pb(mp(res[i],res[i]));
return mp(pa,res);
}
pair <int,vector<int> > leave(int pa)
{
int T=pa;
N-=pa;
while(vec[vec.size()-1].se>N) vec.pop_back();
pa=max(pa,N-vec[vec.size()-1].se);
for(int i=0;i<vec.size();i++) vec[i].se=min(N,vec[i].fi+pa);
vector <pii > tmp;
for(int i=0;i<vec.size();i++)
{
int j=i;
while(j+1<vec.size()&&vec[j+1].fi<=vec[j].se+1) j++;
tmp.pb(mp(vec[i].fi,vec[j].se));
i=j;
}
vec=tmp;
int pos=N;
vector <int> res;
while(1)
{
res.pb(pos);
if(pos==1) break;
pos-=N-pos+2;
if(pos<=1)
{
res.pb(1);
break;
}
pos=calc(pos);
continue;
}
reverse(res.begin(),res.end());
vec.clear();
for(int i=0;i<res.size();i++) vec.pb(mp(res[i],res[i]));
return mp(pa,res);
}
【CEOI 2023】balance
先考虑 \(S=2\),假设有一行有两个数 \(u,v\),我们可以自由决定 \(u,v\) 的顺序。
考虑一个无向图,对于每一行连边 \((u,v)\),要求给每条边定向,被指向的点表示放在第一列,另一个点放在第二列。即,要求每个点入度和出度差至多为 \(1\)。
若每个点度数均为偶数,即可以平均分,此时跑一个欧拉回路,按照欧拉回路定向就行。
如果存在奇数点,则新建一个点,这个点向所有奇数点连边,由于奇数点一定有偶数个,这个新图一定有欧拉回路,按照这个新图的欧拉回路定向就行,每个点入度和出度差是至多为 \(1\) 的。
考虑一般 \(S\),由于 \(S=2^k\),可以分治,每次从中间劈开,将所有数分到左右两边(左边看成第一列,右边看成第二列),套用 \(S=2\) 的方法解决,那么一个出现次数为 \(cnt\) 的数,在某一列的出现次数一定可以表示为 “\(cnt\) 除以若干次 \(2\),每次选择上取整或下去整”,可以归纳证明固定 \(cnt\) 和除的次数之后只有两种取值。
复杂度 \(O(NS \log S)\)。
题外话:对于更一般的 \(S\),即不保证是 \(2\) 的次幂,问题依然可做,如果当前分治区间长度为奇数,可以跑一个类似二分图匹配的东西,转化成偶数的情况。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int N,S,T;
vector <pii > g[1000005];
vector <int> vec[1000005];
int deg[1000005],vis[1000005],cur[1000005];
pii ans[1000005];
int bad[2000005],lim;
void dfs(int u)
{
vis[u]=1;
while(cur[u]<g[u].size())
{
int v=g[u][cur[u]].fi;
int id=g[u][cur[u]].se;
if(bad[id])
{
cur[u]++;
continue;
}
bad[id]=1;
if(id<=lim) ans[id]=mp(u,v);
cur[u]++;
// cout<<u<< " -> "<<v<<"\n";
dfs(v);
}
}
void divide(int L,int R)
{
if(L==R) return;
// cout<<L<<" "<<R<<"\n";
int mid=(L+R)>>1;
int eid=0;
for(int i=1;i<=N;i++) for(int j=L;j<=R;j++)
{
int u=vec[i][j];
deg[u]=vis[u]=cur[u]=bad[u]=0,g[u].clear();
}
deg[0]=vis[0]=cur[0]=bad[0]=0,g[0].clear();
for(int i=1;i<=N;i++)
{
for(int j=L,k=mid+1;j<=mid;j++,k++)
{
int u=vec[i][j],v=vec[i][k];
deg[u]++,deg[v]++,eid++;
// cout<<"edge: "<<u<<" "<<v<<"\n";
if(u==v) g[u].pb(mp(v,eid));
else g[u].pb(mp(v,eid)),g[v].pb(mp(u,eid));
}
}
lim=eid;
for(int i=1;i<=N;i++) for(int j=L;j<=R;j++)
{
int u=vec[i][j];
if(deg[u]%2) eid++,bad[eid]=0,g[0].pb(mp(u,eid)),g[u].pb(mp(0,eid)),deg[0]++,deg[u]++;//,cout<<"edge: "<<0<<" "<<u<<"\n";
}
dfs(0);
for(int i=1;i<=N;i++) for(int j=L;j<=R;j++)
{
int u=vec[i][j];
if(!vis[u]) dfs(u);
}
for(int i=0;i<=eid;i++) bad[i]=0;
eid=0;
for(int i=1;i<=N;i++)
{
for(int j=L,k=mid+1;j<=mid;j++,k++)
{
int u=vec[i][j],v=vec[i][k];
eid++;
int u2=ans[eid].fi,v2=ans[eid].se;
if(u!=u2) swap(vec[i][j],vec[i][k]);
}
}
// system("pause");
divide(L,mid),divide(mid+1,R);
}
void solve()
{
cin>>N>>S>>T;
for(int i=1;i<=N;i++)
{
for(int j=0;j<S;j++)
{
int x;
cin>>x;
vec[i].pb(x);
}
}
// for(int i=1;i<=T;i++) if(deg[i]%2==1) deg[i]++,deg[0]++,g[0].pb(mp(i,N+i)),g[i].pb(mp(0,N+i));
// for(int i=0;i<=T;i++) if(!vis[i]) dfs(i);
divide(0,S-1);
for(int i=1;i<=N;i++)
{
for(int j=0;j<vec[i].size();j++) cout<<vec[i][j]<<" ";
cout<<"\n";
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【CEOI 2023】trade
先考虑第一问,枚举 \(l\),观察到对应的 \(r\) 具有单调性。感性理解:假设存在 \(l_1,l_2,r_1,r_2\),满足 \(l_1 \lt l_2,r_1 \gt r_2\),且 \(r_1\) 是 \(l_1\) 的最优决策点,\(r_2\) 是 \(l_2\) 的最优决策点,由于 \(r_2\) 是 \(l_2\) 的最优决策点,可以得出 \([r_2+1,r_1]\) 这一段在 \([l_2,r_2]\) 这一段中是没用的,那么在 \([l_1,r_2]\) 这个更长的段中也是没用的,所以 \(l_1\) 存在一个最优决策点 \(r_2\),调整后满足单调性。
用主席树维护前 \(k\) 大,可以在 \(O(n \log^2 n)\) 的时间内解决第一问。
考虑第二问,将一个最优的区间记为三元组 \((l,r,lim)\),表示区间 \([l,r]\) 可以成为全局的最优决策,且 \([l,r]\) 的第 \(k\) 大为 \(lim\)。一个数 \(i\) 能被选上当且仅当存在三元组 \((l,r,lim)\) 满足 \(l \le i \le r, a_i \ge lim\),这是一个二维偏序的形式,可以用两个 priority_queue 很简单的实现。
我们只需要考虑决策单调性分治过程中枚举到的区间,做两边分治,一次取最小决策点,一次取最大决策点。
总复杂度 \(O(n \log^2 n)\)。
题外话,还有一个不保真的 \(O(n \sqrt{n \log n} +n \log^2 n)\) 做法,令 \(pre(A,j),suf(A,j)\) 分别表示序列 \(A\) 的每个前缀/后缀中,\((前j大s_i之和)-(c之和)\) 的最大值,这俩好像都是单峰的(?)令 \(A[l,r]\) 表示 \(A\) 的 \(l\) 到 \(r\) 的子段。假设我们有 \(suf(A[l,mid],j)\) 和 \(pre(A[mid+1,r],j)\),那么对于所有跨越 \(mid\) 的区间 \([l',r']\),从中选 \(j'\) 个 \(s_i\),它的最大值可以写成 \(\max_i\{suf(A[l,mid],i)+pre(A[mid+1,r],j'-i)\}\),这个 \(i\) 好像是可以三分的(?)
考虑给序列分块,对于每个块内部预处理 \(pre\) 和 \(suf\),可以暴力;对于每个整块的前缀处理 \(suf\),整块后缀处理 \(pre\),可以用前面说的三分,通过前一块的 \(pre\) 或 \(suf\) 和当前块的信息得出。检查是否答案包含数 \(x\) 的时候,需要枚举 \(x\) 左边选了几个,这个是可以三分的,问题变成了在某个前缀的所有后缀中,\((前j大s_i之和)-(c之和)\) 的最大值,同样可以通过三分当前所在散块中取了几个数求出。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
struct Segtree
{
int ls[10000005],rs[10000005],idx,sz[10000005],sum[10000005];
int build(int l,int r)
{
int u=++idx;
if(l==r) return u;
int mid=(l+r)>>1;
ls[u]=build(l,mid),rs[u]=build(mid+1,r);
return u;
}
int update(int id,int l,int r,int x,int y)
{
int u=++idx;
ls[u]=ls[id],rs[u]=rs[id],sz[u]=sz[id],sum[u]=sum[id];
if(l==r)
{
sz[u]++,sum[u]+=y;
return u;
}
int mid=(l+r)>>1;
if(x<=mid) ls[u]=update(ls[id],l,mid,x,y);
else rs[u]=update(rs[id],mid+1,r,x,y);
sz[u]=sz[ls[u]]+sz[rs[u]];
sum[u]=sum[ls[u]]+sum[rs[u]];
return u;
}
int query(int p,int q,int l,int r,int k)
{
// cout<<"query: "<<p<<" "<<q<<" "<<l<<" "<<r<<" "<<k<<"\n";
if(sz[p]-sz[q]<k) return -INF;
if(l==r)
{
int x=sum[p]/sz[p];
return k*x;
}
int mid=(l+r)>>1;
if(sz[rs[p]]-sz[rs[q]]>=k) return query(rs[p],rs[q],mid+1,r,k);
else return sum[rs[p]]-sum[rs[q]]+query(ls[p],ls[q],l,mid,k-(sz[rs[p]]-sz[rs[q]]));
}
int kth(int p,int q,int l,int r,int k)
{
if(sz[p]-sz[q]<k) return INF;
if(l==r) return sum[p]/sz[p];
int mid=(l+r)>>1;
if(sz[rs[p]]-sz[rs[q]]>=k) return kth(rs[p],rs[q],mid+1,r,k);
else return kth(ls[p],ls[q],l,mid,k-(sz[rs[p]]-sz[rs[q]]));
}
}st;
int Rt[250005];
int n,m,K,b[250005],c[250005],s[250005];
int ans=-INF;
int res[250005],L[250005],R[250005];
vector <pii > op[250005];
void divide(int l1,int r1,int l2,int r2,int tp)
{
if(l1>r1) return;
int val=-2LL*INF,bst=-1;
int mid=(l1+r1)>>1;
for(int i=l2;i<=r2;i++)
{
int nw=(i-mid+1>=K?st.query(Rt[i],Rt[mid-1],1,m,K)-c[i]+c[mid-1]:-INF);
if(nw>val) val=nw,bst=i;
if(tp&&nw==ans)
{
int l=mid,r=i;
int mn=st.kth(Rt[r],Rt[l-1],1,m,K);
op[l].pb(mp(1,mn)),op[r+1].pb(mp(-1,mn));
}
}
res[mid]=val,L[mid]=bst;
ans=max(ans,val);
divide(l1,mid-1,l2,bst,tp),divide(mid+1,r1,bst,r2,tp);
}
void divide2(int l1,int r1,int l2,int r2,int tp)
{
if(l1>r1) return;
int val=-2LL*INF,bst=-1;
int mid=(l1+r1)>>1;
for(int i=l2;i<=r2;i++)
{
int nw=(i-mid+1>=K?st.query(Rt[i],Rt[mid-1],1,m,K)-c[i]+c[mid-1]:-INF);
if(nw>=val) val=nw,bst=i;
if(tp&&nw==ans)
{
int l=mid,r=i;
int mn=st.kth(Rt[r],Rt[l-1],1,m,K);
op[l].pb(mp(1,mn)),op[r+1].pb(mp(-1,mn));
}
}
res[mid]=val,L[mid]=bst;
ans=max(ans,val);
divide2(l1,mid-1,l2,bst,tp),divide2(mid+1,r1,bst,r2,tp);
}
void solve()
{
cin>>n>>K;
for(int i=1;i<=n;i++) cin>>c[i],c[i]+=c[i-1];
for(int i=1;i<=n;i++) cin>>s[i],b[i]=s[i];
sort(b+1,b+1+n),m=unique(b+1,b+1+n)-b-1;
Rt[0]=st.build(1,m);
for(int i=1;i<=n;i++) Rt[i]=st.update(Rt[i-1],1,m,lower_bound(b+1,b+1+m,s[i])-b,s[i]);
divide(1,n-K+1,1,n,0);
cout<<ans<<"\n";
divide(1,n-K+1,1,n,1);
divide2(1,n-K+1,1,n,1);
priority_queue <int> q1,q2;
for(int i=1;i<=n;i++)
{
for(int j=0;j<op[i].size();j++)
{
if(op[i][j].fi==1) q1.push(-op[i][j].se);
else q2.push(-op[i][j].se);
}
while(q1.size()&&q2.size()&&q1.top()==q2.top()) q1.pop(),q2.pop();
if(q1.size()&&-q1.top()<=s[i]) cout<<1;
else cout<<0;
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【CEOI 2023】incursion
将树看成以 \(safe\) 为根的有根树,我们需要找到一条某个点到根的路径。
考虑每次找父亲,如果一个点 \(u\) 的子树大小 \(\le n/2\),那么只有一条边 \((u,v)\),断掉之后 \(v\) 所在连通块大小 \(\ge n/2\),这个连通块就是 \(u\) 子树外的部分,即 \(v\) 是 \(u\) 的父亲。
如果子树大小 \(\gt n/2\),这些点一定构成了一条从根开始不断往下走的链,我们将这条链标记成 \(1\),称它们为黑点,其余点标记成 \(0\),称它们为白点。
如果初始点 \(u\) 为白点,直接找到父亲。
否则检查是否有一个黑儿子,即存在一条边 \((u,v)\) 满足断掉 \((u,v)\) 后 \(v\) 的连通块大小 \(\gt n/2\),如果存在,\(v\) 就是 \(u\) 的黑儿子。如果不存在,则随便走到一个点 \(w\),如果是白点,说明 \(w\) 是 \(u\) 的儿子,否则说明 \(u\) 是 \(w\) 的儿子。
现在我们知道了一个二元组 \((fa,u)\) 表示 \(fa\) 是 \(u\) 的父亲。如果 \(fa\) 是白点,则直接找 \(fa\) 的父亲。
否则,称 \(u\) 的子树外的点为“未知区域”,\(fa\) 的除 \(u\) 以外的至多两条出边,每条边如果走对了(即另一端是 \(fa\) 的父亲),未知区域会减少一部分。观察到如果有两条出边,则目前的未知区域,会被分成两块,每一块对应了一条出边,选择小的那一条边走,如果走错了,未知区域至少会除以 \(2\),即最多 \(O(\log n)\) 次走错。
步数是 \(d+O(\log n)+O(1)\),很显然,卡不满。
Code
#include "incursion.h"
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,tot;
vector <int> g[100005];
int sz[100005],dep[100005];
void dfs0(int u,int fa)
{
sz[u]=1,dep[u]=dep[fa]+1;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v!=fa) dfs0(v,u),sz[u]+=sz[v];
}
}
void inc()
{
tot++;
// assert(tot<=30);
}
int calc(int u,int v) // cut (u,v) and the size with u in it
{
if(dep[u]<dep[v]) return n-sz[v];
return sz[u];
}
vector <int> mark(vector <pii > es,int rt)
{
// cout<<"...\n";
memset(sz,0,sizeof(sz)),memset(dep,0,sizeof(dep));
n=es.size()+1;
for(int i=1;i<=n;i++) g[i].clear();
for(int i=0;i<n-1;i++) g[es[i].fi].pb(es[i].se),g[es[i].se].pb(es[i].fi);
dfs0(rt,0);
vector <int> ret;
for(int i=1;i<=n;i++) ret.pb((sz[i]>n/2?1:0));
// cout<<"mark ok\n";
return ret;
}
int getfa(int u) // u is white
{
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(calc(v,u)>=n/2) return v;
}
assert(0);
return -1;
}
void locate(vector <pii > es,int cur,int col)
{
memset(sz,0,sizeof(sz)),memset(dep,0,sizeof(dep));
n=es.size()+1;
for(int i=1;i<=n;i++) g[i].clear();
for(int i=0;i<n-1;i++) g[es[i].fi].pb(es[i].se),g[es[i].se].pb(es[i].fi);
dfs0(1,0);
vector <int> pa;
while(col==0)
{
pa.pb(cur),cur=getfa(cur);
col=visit(cur);
}
int u=cur;
int v=-1;
if(pa.size()) v=pa.back();
else
{
for(int i=0;i<g[u].size();i++)
{
int w=g[u][i];
if(calc(w,u)>n/2)
{
v=w;
break;
}
}
if(v==-1)
{
int w=g[u][0];
int c=visit(w);
inc();
if(c==0) v=w,visit(u),inc();
else v=u,u=w;
}
}
assert(v!=-1);
while(1)
{
vector <int> vec;
for(int i=0;i<g[u].size();i++) if(g[u][i]!=v&&calc(g[u][i],u)<=n/2) vec.pb(g[u][i]);
// assert(vec.size()<=1);
if(vec.size()==0) return;
if(vec.size()==1)
{
v=u,u=vec[0];
int c=visit(u);
if(c==0)
{
visit(v);
inc();
return;
}
}
if(vec.size()==2)
{
int x=vec[0],y=vec[1];
int sx=calc(y,u),sy=calc(x,u);
if(sx>sy) swap(x,y);
// assert(sx<=n/2&&sy<=n/2);
int c=visit(x);
if(c==1)
{
v=u,u=x;
continue;
}
if(c==0)
{
visit(u);
inc(),inc();
c=visit(y);
if(c==0)
{
visit(u);
inc(),inc();
return;
}
v=u,u=y;
}
}
}
}
//#include "sample_grader.cpp"
【CEOI 2023】avoid
感觉部分分有点烂啊,子任务 \(1,2,3\) 和 \(4\) 的正解可能,关系不大,但能引导一些子任务 \(4\) 的部分分思路。
令 \(n=1000\),答案的二元组为 \((u,v)\),令 \(u \le v\)。
子任务 \(1\),枚举二进制位,每次问所有这一位为 \(0\) 的数,如果有则答案的这一位为 \(0\),否则为 \(1\)。\(R= \log n\),由于不需要在线,\(H=1\)。
子任务 \(2\),考虑一个二分的过程,先找到较小的位置,然后找较大的位置,注意找较大的位置的时候,如果询问集合包含已经确定的小的位置,需要把它删掉。过程需要在线,\(R=H=2\log n\)。
子任务 \(3\),枚举每一位,询问所有这一位为 \(0\) 的数,和所有这一位为 \(1\) 的数,然后读取结果。如果某一位,\(0\) 有 \(1\) 没有,则 \(u,v\) 的这一位都为 \(0\); \(0\) 没有 \(1\) 有同理。如果 \(0,1\) 都有,则说明 \(u,v\) 这一位不相同,不妨令这些位的最高一位 \(d\) 上,\(u\) 为 \(0\),\(v\) 为 \(1\)。我们只需要确定 \(u\) 所有的位 \(i\) 和位 \(d\) 是否相同,枚举 \(i\),询问所有位 \(i\) 和位 \(d\) 都为 \(0\) 的数,\(R=3\log n,H=2\)。
子任务 \(4\),首先有一个随机化的做法,每一次询问随机一个集合,每个数等概率被包含,令概率为 \(P\),则一个数在一次询问中被确定 \(\notin \{u,v\}\) 的概率是 \(P \cdot (1-P)^2\),取 \(P=1/3\) 最优,实测 \(R=56\)。
借用子任务 \(3\) 的想法,枚举每一位,询问所有这一位为 \(0\) 的数,和所有这一位为 \(1\) 的数,然后再随机加一点集合,实测 \(R=41\)。
换一个角度考虑,对于每一个 \([1,n]\) 的数 \(i\),确定一个二进制数 \(A_i\),其中第 \(d\) 位表示了在第 \(d\) 次询问中是否包含 \(i\)。询问的结果可以看做得到一个二进制数 \(A_u | A_v\),其中 \(|\) 表示按位或,也就是说,需要找到一个尽可能短的二进制数组 \(A\),满足对于任意 \(u,v(u \le v)\),\(A_u|A_v\) 两两不同。
嗯,其实这是一道提交答案题,把 \(A\) 在本地求出来后打表,询问后暴力枚举找答案就行。
一个很直观的感觉是 \(A_u\) 的 \(popcount\) 在 \(1/3\) 长度左右,在这个数字两边试试,尝试一些随机化,逐个确定 \(A_i\),能得到 \(R=30\) 左右的做法。观察到如果一个二进制数加入后不合法,那么在之后的过程中同样也不合法,想做到更优的话需要实现一个能快速从一个序列中随机选一个数并删除的东西。我的实现方法是维护序列长度 \(x\) 和里面剩余的数个数 \(y\),每次随机的期望随机次数是 \(x/y\),如果 \(y \ge x/2\),就不管它,否则把序列重构一次,这样每个数均摊是 \(O(1)\) 的,可以得到 \(R=28\) 左右的做法。
还有一个不是很直观的感觉,\(A_i |A_j\) 的 \(popcount\) 不能太小,不然很容易撞。若 \(R=26\),我取的阈值是 \(A_i\) 的 \(popcount =8\),\(A_i|A_j\) 的 \(popcount \ge 11\),从小到大尝试加入,能得到 \(992\) 个 \(A_i\),就差 \(8\) 个,很恶心。
考虑给这个过程加点乱搞, 初始令 \(A_i|A_j\) 的 \(popcount\),阈值为 \(15\),然后找一轮,找完之后将阈值减一点,比如 \(15 \rightarrow 14 \rightarrow 11\),每次遇到能加入的数,以 \(99\%\) 的概率加入,多跑几次,我得到 \(996\) 个 \(A_i\)。
遍历一遍所有 \(2^R\) 个数,检查是否能加入,将所有能加入的数找出来,发现很少(我得到了 \(7\) 个),枚举所有可能的情况,加入 \(996\) 个 \(A_i\) 并检查,然后就找到解了。
打的表太长,不放代码了。
【JOI Open 2023】Ancient Machine 2
令 \(n=1000\),即答案长度。
由于不需要优化询问次数,且询问次数限制为 \(n\),考虑问出 \(n\) 个方程把答案解出来。
一个想法是选一些位置,问出它们的异或和。询问的自动机大概是有左右两部分,读取到这些位置的时候,如果这个位置为 \(1\),就跳到另一部分,最后根据落在哪个部分得到某些位置 \(1\) 的个数奇偶性,也就是这些位置的异或和。
搞两个长度为 \(x\) 的环,对于每个环上的第 \(y\) 个节点,\(1\) 边指向另一个环的下一个节点,其余边均指向当前环的下一个节点,询问这个自动机,可以得到所有模 \(x\) 为 \(y\) 的位置上的异或和,只需要选 \(n\) 个二元组 \((x,y)\) 就行。
不幸的是,这个矩阵不满秩,如果想要满秩的话,\(x\) 的最大值能达到 \(54\),即 \(M=108\),不能通过。
考虑先确定前 \(100\) 位,这个是简单的,确定第 \(i\) 位时,节点 \(j(j \lt i)\) 两条边指向 \(j+1\),节点 \(i\),\(0\) 边指向 \(i+1\),\(1\) 边指向 \(i+2\),\(i+1,i+2\) 的两条边均指向自己。
然后考虑确定后 \(100\) 位,确定倒数第 \(i\) 位时,询问 \(0+(倒数第 i-1 位开始的后缀)\) 的 KMP 自动机,看是否在最后一位上匹配。如果匹配则第 \(i\) 位是 \(0\),否则是 \(1\)。
对于中间 \(800\) 位解方程,正好就卡进去了。
用 bitset 做消元,时间复杂度 \(O(n^3/w)\)。
Code
#include "ancient2.h"
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
namespace
{
int ans[1005];
int to0[1005],to1[1005];
bitset <1005> mask[1005],tmp;
mt19937 rnd(114514);
}
bool chk(bitset <1005> nw)
{
for(int i=0;i<1000;i++) if(nw[i])
{
if(!mask[i][i]) return 1;
nw^=mask[i];
}
return 0;
}
void ins(bitset <1005> nw)
{
for(int i=0;i<1000;i++) if(nw[i])
{
if(!mask[i][i])
{
// cout<<"added: "<<i<<"\n";
mask[i]=nw;
return;
}
nw^=mask[i];
}
}
int nxt[1005];
bool chk_suf(vector <int> vec)
{
int m=vec.size();
vec.insert(vec.begin(),-1);
for(int i=2,j=0;i<=m;i++)
{
while(j&&vec[i]!=vec[j+1]) j=nxt[j];
if(vec[i]==vec[j+1]) j++;
nxt[i]=j;
}
vec.pb(-1);
for(int j=0;j<=m;j++)
{
int t=j;
while(t&&vec[t+1]!=0) t=nxt[t];
if(vec[t+1]==0) t++;
to0[j]=t;
}
for(int j=0;j<=m;j++)
{
int t=j;
while(t&&vec[t+1]!=1) t=nxt[t];
if(vec[t+1]==1) t++;
to1[j]=t;
}
vector <int> A,B;
A.clear(),B.clear();
for(int i=0;i<=m;i++) A.pb(to0[i]),B.pb(to1[i]);
return Query(m+1,A,B)==m;
}
string Solve(int N)
{
vector <int> A,B;
for(int i=0;i<100;i++)
{
for(int j=0;j<i;j++) to0[j]=to1[j]=j+1;
to0[i]=i+1,to1[i]=i+2;
to0[i+1]=to1[i+1]=i+1,to0[i+2]=to1[i+2]=i+2;
A.clear(),B.clear();
for(int j=0;j<i+3;j++) A.pb(to0[j]),B.pb(to1[j]);
if(Query(i+3,A,B)==i+1) ans[i]=0;
else ans[i]=1;
mask[i][i]=1,mask[i][1000]=ans[i];
}
for(int i=999;i>=900;i--)
{
vector <int> vec;
vec.pb(1);
for(int j=i+1;j<1000;j++) vec.pb(ans[j]);
ans[i]=chk_suf(vec);
mask[i][i]=1,mask[i][1000]=ans[i];
// cout<<i<<" "<<ans[i]<<"\n";
}
for(int _=1,u=0,v=1;_<=800;_++)
{
tmp.reset();
for(int i=0;i<1000;i++) if(i%v==u) tmp[i]=1;
if(chk(tmp))
{
for(int i=0;i<v;i++) to0[i]=to1[i]=(i+1)%v,to0[v+i]=to1[v+i]=v+(i+1)%v;
to1[u]=v+(u+1)%v,to1[v+u]=(u+1)%v;
A.clear(),B.clear();
for(int i=0;i<2*v;i++) A.pb(to0[i]),B.pb(to1[i]);
if(Query(2*v,A,B)<v) tmp[1000]=0;
else tmp[1000]=1;
// cout<<_<<" "<<2*v<<"\n";
// cout<<"... "<<u<<" "<<v<<" "<<tmp[1000]<<"\n";
// cout<<"add: "<<tmp<<"\n";
ins(tmp);
}
else _--;
u++;
if(u==v) v++,u=0;
}
// for(int i=0;i<1000;i++)
// {
// for(int j=0;j<=1000;j++) cout<<mask[i][j];
// cout<<"\n";
// }
for(int i=999;i>=0;i--)
{
ans[i]=mask[i][1000];
for(int j=999;j>i;j--) if(mask[i][j]) ans[i]^=ans[j];
// cout<<i<<" "<<ans[i]<<"\n";
}
string ret="";
for(int i=0;i<N;i++)
{
if(ans[i]) ret+='1';
else ret+='0';
}
// cout<<ret<<"\n";
return ret;
}
//#include "grader.cpp"
【JOI Open 2023】Garden
考虑模 \(D\) 意义下的限制,选择的矩形区域可以看做两个模 \(D\) 意义下的区间 \([L_x,R_x],[L_y,R_y]\),对于 A 类图形 \((x,y)\) 可以看做 \(x \in [L_x,R_x]\) 且 \(y \in [L_y,R_y]\),对于 B 类图形 \((x,y)\) 可以看做 \(x \in [L_x,R_x]\) 或 \(y \in [L_y,R_y]\)。
枚举 \([L_x,R_x]\),将所有 A 类图形和目前不满足条件的 B 类图形的 \(y\),放到一个长度为 \(D\) 的环上,需要找到一个最短的环上区间覆盖所有点,即求所有相邻两点之间的距离最大值。
枚举 \(L_x\),如果能求出每个 \(y\),在 \(R_x\) 扩大到什么时候,环上区间不需要覆盖 \(y\)(即没有 A 类图形有对应的 \(y\),且所有拥有对应的 \(y\) 的 B 类图形已经被满足)。\(R_x\) 扩大的时候可能会有一些删除操作,用环状链表维护就行。
令 \(T(L,y)\) 表示若 \(L_x=l\),\(R_x\) 在什么时候会把 \(y\) 删除,观察到 \(T(L,y)\) 在 \(L\) 增大的时候有单调性,用双指针预处理就行。
复杂度 \(O(D^2)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int N,M,D;
struct Circ
{
int prv[5005],nxt[5005],res,len;
void init(vector <int> vec)
{
res=-1;
len=vec.size();
for(int i=0;i<vec.size();i++)
{
prv[vec[i]]=vec[(i-1+len)%len],nxt[vec[i]]=vec[(i+1)%len];
res=max(res,(nxt[vec[i]]-vec[i]+D)%D);
}
if(len<=1) res=D;
}
void del(int x)
{
len--;
if(len<=1) res=D;
int u=prv[x],v=nxt[x];
res=max(res,(v-u+D)%D);
nxt[u]=v,prv[v]=u;
}
}circ;
int X0[500005],Y0[500005],X1[500005],Y1[500005];
vector <int> hasy[10005],pos[5005],op[10005];
int has[5005];
int limX[5005];
int prv[5005][5005],idx[5005];
int ans;
void solve()
{
cin>>N>>M>>D;
ans=D*D;
for(int i=1;i<=N;i++) cin>>X0[i]>>Y0[i];
for(int i=1;i<=M;i++) cin>>X1[i]>>Y1[i],hasy[X1[i]].pb(Y1[i]),hasy[X1[i]+D].pb(Y1[i]),pos[Y1[i]].pb(X1[i]),pos[Y1[i]].pb(X1[i]+D);
for(int i=0;i<D;i++) sort(pos[i].begin(),pos[i].end()),idx[i]=(int)(pos[i].size())-1;
for(int i=D-1;i>=0;i--) for(int j=0;j<D;j++)
{
while(idx[j]>=0&&pos[j][idx[j]]>i+D-1) idx[j]--;
prv[i][j]=(idx[j]>=0?pos[j][idx[j]]:-1);
// cout<<"... "<<i<<" "<<j<<" "<<prv[i][j]<<"\n";
}
memset(has,0,sizeof(has));
for(int i=1;i<=N;i++) has[X0[i]]=1;
for(int i=0;i<D;i++)
{
limX[i]=1;
for(int j=0;j<D;j++) if(has[j]) limX[i]=max(limX[i],(j-i+D)%D+1);
}
memset(has,0,sizeof(has));
for(int i=1;i<=N;i++) has[Y0[i]]=1;
for(int l=0;l<D;l++)
{
vector <int> vec;
for(int i=0;i<2*D;i++) op[i].clear();
// cout<<l<<":\n";
for(int j=0;j<D;j++) if(prv[l][j]>=l||has[j])
{
// cout<<j<<" ";
vec.pb(j);
if(!has[j]) op[prv[l][j]].pb(j);
}
// cout<<"\n";
circ.init(vec);
for(int r=l;r<=D+l-1;r++)
{
// cout<<"... "<<l<<" "<<r<<"\n";
int Lx=max(r-l+1,limX[l]);
for(int i=0;i<op[r].size();i++) circ.del(op[r][i]);//,cout<<"- "<<op[r][i]<<"\n";
// cout<<circ.res<<"\n";
int Ly=D-circ.res+1;
// cout<<l<<" "<<r<<" "<<Lx<<" "<<Ly<<"\n";
ans=min(ans,Lx*Ly);
}
// system("pause");
}
cout<<ans<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【JOISC 2023】曲奇
先考虑确定每个盒子大小之后,如何检验是否合法。
令有 \(K\) 个盒子,大小为 \(C_1,C_2,\cdots,C_K\)。很直观的感觉是先装大的盒子,先装出现次数多的物品。因为小盒子和出现次数小的物品更灵活。
假设装了 \(L\) 个盒子,大小为 \(C_1,C_2,\cdots,C_k\),那么有 \(\sum_{i=1}^k C_i \le \sum_{i=1}^k \min\{A_i,k\}\),容易发现这个条件是充要的。
考虑 dp,令 \(dp(i,j,k)\) 表示选了 \(i\) 个盒子,最小的盒子大小为 \(j\),盒子大小之和为 \(k\),是否可行。观察到 \(j \le (A_1+A_2+ \cdots +A_n)/i\),所以总状态数是 \(n^2 \ln n\) 的,用 bitset 优化,复杂度 \(O(n^2 \ln n/w)\),而 \(k \ge i \cdot j\),手写 bitset 的话会更快,但是没必要。
构造方案先装大盒子和出现次数多的物品就行。
Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
bitset <15005> tmp,lim[15005];
vector <bitset <15005> > dp[15005];
int n,m,N;
pii a[15005];
int b[15005],c[15005];
void solve()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i].fi,a[i].se=i;
c[a[i].fi]+=a[i].fi,N+=a[i].fi;
}
for(int i=1;i<=N;i++) c[i]+=c[i-1];
for(int i=1;i<=n;i++) for(int j=1;j<a[i].fi;j++) c[j]+=j;
sort(a+1,a+1+n),reverse(a+1,a+1+n);
cin>>m;
for(int i=1;i<=m;i++) cin>>b[i];
for(int i=0;i<=N;i++)
{
dp[i].pb(tmp);
for(int j=1;j<=m;j++) if(i*b[j]<=N) dp[i].pb(tmp);
}
dp[0][m][0]=1;
for(int i=0;i<=N;i++) tmp[i]=1;
lim[0]=tmp;
for(int i=N,j=N;i>=1;i--)
{
while(j>c[i]) tmp[j]=0,j--;
lim[i]=tmp;
}
int ans=-1;
int u=-1,v=-1,w=-1;
for(int i=0;i<N;i++)
{
for(int j=(int)(dp[i].size())-1;j>=1;j--)
{
tmp=(dp[i][j]<<b[j])&lim[i+1];
if(j<dp[i+1].size())
{
dp[i+1][j]|=tmp;
if(tmp[N])
{
ans=i+1,u=i+1,v=j,w=N;
break;
}
}
if(j) dp[i][j-1]|=dp[i][j];
}
if(ans!=-1) break;
}
cout<<ans<<"\n";
// cout<<u<<" "<<v<<" "<<w<<"\n";
if(ans==-1) return;
vector <int> vec;
for(int _=1;_<=ans;_++)
{
if(w-b[v]>=0&&dp[u-1][v][w-b[v]]) vec.pb(b[v]),u--,w-=b[v];
else
{
assert(dp[u][v+1][w]);
v++,_--;
}
}
reverse(vec.begin(),vec.end());
set <pii > S;
for(int i=1;i<=n;i++) S.insert(a[i]);
for(int i=0;i<vec.size();i++)
{
vector <pii > t;
auto it=S.end();
cout<<vec[i]<<" ";
while(vec[i]--)
{
it--;
t.pb(*it);
S.erase(it);
it=S.end();
}
for(int j=0;j<t.size();j++)
{
cout<<t[j].se<<" ";
t[j].fi--;
if(t[j].fi) S.insert(t[j]);
}
cout<<"\n";
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【ARC146E】Simple Speed
考虑按值从小到大插入,假设目前插入了数 \(1\) 到 \(x\),当前序列相邻两个数要么差为 \(1\),要么都为 \(x\)。插入 \(x+1\) 的时候需要在每一对相邻的 \(x\) 之间都插入 \(\ge 1\) 个,剩下的数一定放在开头或末尾。
令 \(dp(i,j,0/1/2)\) 表示目前插入了数 \(1\) 到 \(i\),有 \(j\) 对相邻的 \(i\),开头末尾有 \(0/1/2\) 个是 \(i\),的方案数。转移枚举开头或结尾是否放了 \(i+1\),就可以算出 \(j\),系数可以插板法。观察到 \(j\) 是 \(O(1)\) 级别的,直接用 vector 或者其它东西记录有值的地方。复杂度 \(O(n)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int fpow(int x,int b)
{
if(x==0) return 0;
if(b==0) return 1;
int res=1;
while(b>0)
{
if(b&1) res=1LL*res*x%mod;
x=1LL*x*x%mod;
b>>=1;
}
return res;
}
int fac[500005],ifac[500005];
int C(int x,int y)
{
if(y>x) return 0;
if(x==y||y==0) return 1;
return 1LL*fac[x]*ifac[x-y]%mod*ifac[y]%mod;
}
void init(int x)
{
fac[0]=fac[1]=1;
for(int i=2;i<=x;i++) fac[i]=1LL*fac[i-1]*i%mod;
ifac[x]=fpow(fac[x],mod-2);
for(int i=x-1;i>=0;i--) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
int g(int x,int y)
{
return C(x+y-1,y-1);
}
int n,a[200005];
vector <array<int,3> > vec[2];
void solve()
{
init(500000);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int nw=0;
vec[nw].pb({a[1]-1,2,1});
for(int i=2;i<=n;i++)
{
nw^=1;
vec[nw].clear();
for(int _=0;_<vec[nw^1].size();_++)
{
int j=vec[nw^1][_][0],k=vec[nw^1][_][1],val=vec[nw^1][_][2];
// cout<<i-1<<" "<<j<<" "<<k<<" "<<val<<"\n";
for(int k2=0;k2<=k;k2++)
{
int has=a[i]-k2;
if(has<j) continue;
has-=j;
int cnt=k2+j;
vec[nw].pb({has,k2,val*g(has,cnt)%mod*C(k,k2)%mod});
}
}
sort(vec[nw].begin(),vec[nw].end());
vector <array<int,3> > tmp;
tmp.clear();
for(int j=0;j<vec[nw].size();j++)
{
int l=j,sum=0;
while(l<vec[nw].size()&&vec[nw][l][0]==vec[nw][j][0]&&vec[nw][l][1]==vec[nw][j][1]) sum=(sum+vec[nw][l][2])%mod,l++;
l--;
tmp.pb({vec[nw][j][0],vec[nw][j][1],sum});
j=l;
}
vec[nw]=tmp;
}
int res=0;
for(int j=0;j<vec[nw].size();j++) if(vec[nw][j][0]==0) res=(res+vec[nw][j][2])%mod;
cout<<res<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【ARC068D】Solitaire
将 \(1\) 到 \(N\) 插入 deque 后,deque 里面一定形如一个前面递减后面递增的序列。把 deque 中的数全部取出来后,在位置 \(K\) 之前一定能划分成两个单调递减子序列,位置 \(K\) 为 \(1\),位置 \(K\) 之后一定是递减序列,且能和前半部分不包含位置 \(K\) 的那个子序列拼成一个子序列。
问题转化成:求 \(a_K=1\) 且整个排列能分成两个单调递减子序列的 \(1\) 到 \(N\) 的排列数量。
分成两个单调递减子序列,等价于最长上升子序列 \(\le 2\),令 \(dp(i,j)\) 表示长度为 \(i\),所有长度为 \(2\) 的上升子序列,末端最小为 \(j\) 的方案数。转移是前缀和的形式。
复杂度 \(O(N^2)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int fpow(int x,int b)
{
if(x==0) return 0;
if(b==0) return 1;
int res=1;
while(b>0)
{
if(b&1) res=1LL*res*x%mod;
x=1LL*x*x%mod;
b>>=1;
}
return res;
}
int fac[300005],ifac[300005];
int C(int x,int y)
{
if(y>x) return 0;
if(x==y||y==0) return 1;
return 1LL*fac[x]*ifac[x-y]%mod*ifac[y]%mod;
}
void init(int x)
{
fac[0]=fac[1]=1;
for(int i=2;i<=x;i++) fac[i]=1LL*fac[i-1]*i%mod;
ifac[x]=fpow(fac[x],mod-2);
for(int i=x-1;i>=0;i--) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
int n,k;
int dp[2005][2005],sum[2005];
void solve()
{
cin>>n>>k;
init(10000);
int ans=0;
dp[1][n+1]=1;
for(int i=1;i<n;i++)
{
memset(sum,0,sizeof(sum));
for(int j=1;j<=n+1;j++) sum[j]=(sum[j-1]+dp[i][j])%mod;//,sum[j][1]=(sum[j-1][1]+dp[i][j][1])%mod;
int Lj=1,Rj=i+1;
if(i+1==k) Rj=1;
if(i+1>k) Lj=2;
// cout<<i+1<<" "<<Lj<<" "<<Rj<<"\n";
for(int j=Lj;j<=Rj;j++)
{
if(j>=2) dp[i+1][j]=(dp[i+1][j]+sum[n+1]-sum[j-1]+mod)%mod;
else
{
for(int l=2;l<=i+1;l++) dp[i+1][l]=(dp[i+1][l]+dp[i][l-1])%mod;
dp[i+1][n+1]=(dp[i+1][n+1]+dp[i][n+1])%mod;
}
// dp[i+1]
// dp[i+1][j][0]=(sum[n][0]-sum[j-1][0]+sum[n][1]-sum[j-1][1]+mod+mod)%mod;
// if(i+1<=k+1) dp[i+1][j][1]=sum[j-1][0];
// cout<<i+1<<" "<<j<<" "<<dp[i+1][j][0]<<" "<<dp[i+1][j][1]<<"\n";
}
// for(int j=1;j<=n+1;j++) cout<<i+1<<" "<<j<<" "<<dp[i+1][j]<<" \n";
// system("pause");
}
for(int i=1;i<=n+1;i++) ans=(ans+dp[n][i])%mod;
// cout<<ans<<"\n";
// for(int i=1;i<=k;i++)
// {
// int nw=C(n-1,i-1);
// nw=nw*C(k-1,i-1)%mod;
// ans=(ans+nw)%mod;
// }
//
cout<<ans*fpow(2,max(0LL,n-k-1))%mod<<"\n";
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【ARC108E】Random IS
令 \(a_0=0,a_{N+1}=N+1\),假设第一步选择了 \(mid\),能观察到 \([0,mid]\) 和 \([mid,N+1]\) 两段是独立的
令 \(dp(l,r)\) 表示区间 \([l,r]\) 的答案,转移 \(dp(l,r)=\frac{\sum_{k=l+1}^{r-1} [a_l \lt a_k \lt a_r](dp(l,k)+dp(k,r)-1)}{\sum_{k=l+1}^{r-1} [a_l \lt a_k \lt a_r]}\)。
转移是二维偏序的形式,对每个左端点和右端点开一个 BIT 维护,注意特判合法 \(k\) 数量为 \(0\) 的情况。
复杂度 \(O(n^2 \log n)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int fpow(int x,int b)
{
if(x==0) return 0;
if(b==0) return 1;
int res=1;
while(b>0)
{
if(b&1) res=1LL*res*x%mod;
x=1LL*x*x%mod;
b>>=1;
}
return res;
}
struct BIT
{
int t[2005];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int d)
{
x++;
for(int i=x;i<=2002;i+=lowbit(i)) t[i]=(t[i]+d)%mod;
}
int query(int x)
{
x++;
int res=0;
for(int i=x;i>=1;i-=lowbit(i)) res=(res+t[i])%mod;
return res;
}
} L[2005],R[2005],cnt[2005];
int n,a[2005],dp[2005][2005];
void solve()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
a[0]=0,a[n+1]=n+1;
for(int i=0;i<=n+1;i++) dp[i][i]=1,cnt[i].update(a[i],1),L[i].update(a[i],1),R[i].update(a[i],1);
for(int len=2;len<=n+2;len++) for(int l=0;l+len-1<=n+1;l++)
{
int r=l+len-1;
// cout<<"try: "<<l<<" "<<r<<"\n";
if(a[l]<a[r])
{
int u=a[l]+1,v=a[r]-1;
if(u>v) dp[l][r]=2;
else
{
int c=cnt[l].query(v)-cnt[l].query(u-1);
// cout<<"cnt: "<<l<<" "<<r<<" "<<c<<"\n";
if(c) dp[l][r]=(L[l].query(v)-L[l].query(u-1)+R[r].query(v)-R[r].query(u-1)-c+mod+mod+mod)%mod*fpow(c,mod-2)%mod;
else dp[l][r]=2;
}
L[l].update(a[r],dp[l][r]),R[r].update(a[l],dp[l][r]);
}
cnt[l].update(a[r],1);
// cout<<"dp: "<<l<<" "<<r<<" "<<dp[l][r]<<"\n";
}
cout<<(dp[0][n+1]-2+mod)%mod<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【ARC118E】Avoid Permutations
考虑容斥,令 \(dp(x,y,k)\) 表示考虑从 \((0,0)\) 走到 \((x,y)\) 的路径,钦定撞上了 \(k\) 个 \((i,P_i)\)。
由于每一行每一列只能放一个 \((i,P_i)\),所以还要多记两维 \(dp(x,y,k,0/1,0/1)\) 分别表示当前行是否放了,当前列是否放了,复杂度 \(O(n^3)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n,a[205],bad[205];
int dp[205][205][205][2][2];
void add(int &x,int y)
{
x=(x+y)%mod;
}
void solve()
{
cin>>n;
int tot=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(a[i]==-1) tot++;
else bad[a[i]]=1;
}
dp[0][0][0][0][0]=1;
for(int i=0;i<=n+1;i++) for(int j=0;j<=n+1;j++) for(int c=0;c<=n;c++) for(int ti=0;ti<2;ti++) for(int tj=0;tj<2;tj++) if(dp[i][j][c][ti][tj])
{
if(1<=i&&i<=n&&1<=j&&j<=n&&(a[i]==j||(a[i]==-1&&!bad[j]))&&!ti&&!tj)
{
if(a[i]==-1) add(dp[i][j][c+1][1][1],mod-dp[i][j][c][ti][tj]);
else add(dp[i][j][c][1][1],mod-dp[i][j][c][ti][tj]);
}
if(i<n+1) add(dp[i+1][j][c][0][tj],dp[i][j][c][ti][tj]);
if(j<n+1) add(dp[i][j+1][c][ti][0],dp[i][j][c][ti][tj]);
}
int ans=0;
for(int i=0,fac=1;i<=tot;i++)
{
int c=tot-i;
ans=(ans+fac*(dp[n+1][n+1][c][0][0]+dp[n+1][n+1][c][0][1]+dp[n+1][n+1][c][1][0]+dp[n+1][n+1][c][1][1]))%mod;
// cout<<i<<" "<<fac<<" "<<c<<"\n";
fac=fac*(i+1)%mod;
}
cout<<ans<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【ARC110E】Shorten ABC
考虑形式化这个操作,需要找到 \(3\) 个数和某种运算,每个数参与运算之后不能得到自己,且容易算区间和。令 \(A=1,B=2,C=3\),一次操作可以看成用 \(S_i \space \text{xor} \space S_{i+1}\) 替换 \(S_i\) 和 \(S_{i+1}\),最终状态每一个字符对应了一个区间,令 \(dp(i)\) 表示前 \(i\) 个字符能生成多少种最终状态,转移为了避免算重,对于同一个异或和取最靠后的位置。
复杂度 \(O(n \log n)\) 或 \(O(n)\),取决于实现。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define pii pair<long long,long long>
#define mp make_pair
#define pb push_back
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const int INF=1e18;
int n;
char S[1000005];
int a[1000005],dp[1000005],lim[1000005],sum[1000005];
vector <int> pos[4];
void solve()
{
cin>>n;
cin>>(S+1);
bool ok=1;
for(int i=1;i<=n;i++) a[i]=(S[i]-'A')+1,ok&=(a[i]==a[1]);
if(ok)
{
cout<<"1\n";
return;
}
dp[0]=1;
for(int i=1;i<=n;i++)
{
int j=i;
while(j<=n&&a[j]==a[i]) j++;
j--;
for(int l=i;l<=j;l++) lim[l]=i-2;
i=j;
}
// for(int i=1;i<=n;i++) cout<<lim[i]<<" ";
// cout<<"\n";
// pos[0].pb(0);
for(int i=1;i<=n;i++) sum[i]=sum[i-1]^a[i],pos[sum[i]].pb(i);
for(int i=1;i<=n;i++)
{
dp[i]=dp[i-1];
for(int j=0;j<=3;j++) if(j!=sum[i]&&j!=sum[i-1])
{
int p=upper_bound(pos[j].begin(),pos[j].end(),lim[i])-pos[j].begin()-1;
if(p<0) continue;
// cout<<j<<" "<<pos[j][p]<<" -> "<<i<<"\n";
dp[i]=(dp[i]+dp[pos[j][p]])%mod;
}
if(i>1&&sum[i]) dp[i]=(dp[i]+1)%mod;
// cout<<i<<" "<<dp[i]<<"\n";
}
cout<<dp[n]<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【AGC033D】Complexity
最朴素的 dp:\(dp(x_1,y_1,x_2,y_2)\) 表示左上角 \((x_1,y_1)\),右下角 \((x_2,y_2)\) 的子矩形的 complexity。
容易发现答案不超过 \(\log n\),每次劈成相等的两半就行。考虑另一个 dp:\(dp(c,x_1,y_1,x_2)\),表示一个 complexity 不超过 \(c\),左上角 \((x_1,y_1)\),右下角的 \(x\) 坐标为 \(x_2\),右下角的 \(y\) 坐标最大是多少。
转移,竖着劈是简单的,横着劈要枚举劈在什么位置,确定 \(c,x_1,y_1\) 后,这个决策点和 \(x_2\) 是有单调性的,复杂度 \(O(n^3 \log^2 n)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,m;
int dp[2][205][205][205];
char g[205][205];
int R[205][205];
void divide(int nw,int x,int y,int l1,int r1,int l2,int r2)
{
if(l1>r1) return;
int mid=(l1+r1)>>1;
int bst=-1;
for(int i=l2;i<=min(mid-1,r2);i++)
{
int val=min(dp[nw^1][x][y][i],dp[nw^1][i+1][y][mid]);
if(val>dp[nw][x][y][mid]) bst=i,dp[nw][x][y][mid]=val;
}
if(dp[nw][x][y][mid]==-1) divide(nw,x,y,l1,mid-1,l2,r2);
else divide(nw,x,y,l1,mid-1,l2,bst),divide(nw,x,y,mid+1,r1,bst,r2);
}
void solve()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>(g[i]+1);
memset(dp,-1,sizeof(dp));
for(int x=1;x<=n;x++) for(int y=1;y<=m;y++)
{
int z=y;
while(z<=m&&g[x][y]==g[x][z]) z++;
z--;
R[x][y]=z;
}
for(int x=1;x<=n;x++) for(int y=1;y<=m;y++)
{
int z=x;
while(z<=n&&g[x][y]==g[z][y]) z++;
z--;
for(int l=x,mn=inf;l<=z;l++)
{
mn=min(mn,R[l][y]);
dp[0][x][y][l]=mn;
}
}
if(dp[0][1][1][n]==m)
{
cout<<"0\n";
return;
}
int nw=0;
for(int ans=1;;ans++)
{
nw^=1;
memset(dp[nw],-1,sizeof(nw));
for(int x=1;x<=n;x++) for(int y=1;y<=m;y++) divide(nw,x,y,x+1,n,x,n-1);
for(int x=1;x<=n;x++) for(int y=1;y<=m;y++) for(int z=1;z<=n;z++) if(dp[nw^1][x][y][z]!=-1)
{
if(dp[nw^1][x][y][z]==m) dp[nw][x][y][z]=m;
else
{
int y2=dp[nw^1][x][y][z]+1;
dp[nw][x][y][z]=max(dp[nw][x][y][z],dp[nw^1][x][y2][z]);
}
}
if(dp[nw][1][1][n]==m)
{
cout<<ans<<"\n";
return;
}
}
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【ARC092F】Two Faced Edges
一条边 \((u,v)\) 反转后 scc 数量改变,需要满足下面两个条件之一:
- \(v\) 可以到达 \(u\)。
- 删去这条边后 \(u\) 能到达 \(v\)。
只满足第一个条件,scc 数量减少,只满足第二个条件,scc 数量增多。
第一个条件可以简单 bfs 解决,第二个条件只需要对每个 \(u\) 的每条出边的邻居处理,删掉 \(u\) 的所有出边,以任意顺序排列邻居,依次从每个邻居开始 BFS,正着排列跑一遍,反着排列跑一遍就行。
复杂度 \(O(NM)\),bitset 优化可以做到 \(O(N^3/w)\)。
Code
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n,m;
bitset <1005> g[1005],vis,now,ok1[1005],ok2[1005];
vector <pii > es;
vector <int> G[1005];
queue <int> q;
void bfs()
{
while(q.size())
{
int u=q.front();
q.pop();
now=vis&g[u];
for(int i=now._Find_first();i<=n;i=now._Find_next(i)) vis[i]=0,q.push(i);
}
}
void solve()
{
cin>>n>>m;
while(m--)
{
int u,v;
cin>>u>>v;
es.pb(mp(u,v)),g[u][v]=1,G[u].pb(v);
}
for(int i=1;i<=n;i++)
{
while(q.size()) q.pop();
for(int j=1;j<=n;j++) vis[j]=1;
vis[i]=0,q.push(i),bfs();
for(int j=1;j<=n;j++) if(!vis[j]) ok1[i][j]=1;
}
for(int i=1;i<=n;i++)
{
while(q.size()) q.pop();
for(int j=1;j<=n;j++) vis[j]=1;
vis[i]=0;
for(int j=0;j<G[i].size();j++)
{
while(q.size()) q.pop();
q.push(G[i][j]),vis[G[i][j]]=0,bfs();
for(int l=j+1;l<G[i].size();l++) if(!vis[G[i][l]]) ok2[i][G[i][l]]=1;
}
}
for(int i=1;i<=n;i++)
{
while(q.size()) q.pop();
for(int j=1;j<=n;j++) vis[j]=1;
vis[i]=0;
reverse(G[i].begin(),G[i].end());
for(int j=0;j<G[i].size();j++)
{
while(q.size()) q.pop();
q.push(G[i][j]),vis[G[i][j]]=0,bfs();
for(int l=j+1;l<G[i].size();l++) if(!vis[G[i][l]]) ok2[i][G[i][l]]=1;
}
}
for(int i=0;i<es.size();i++)
{
int u=es[i].fi,v=es[i].se;
if(ok1[v][u]!=ok2[u][v]) cout<<"diff\n";
else cout<<"same\n";
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}