2023.7 做题笔记
【CCO 2021】Travelling Merchant
令 \(dp(u)\) 表示从节点 \(u\) 出发,最少需要多少的起始资金才能无限行走下去。转移 \(dp(u)=\min_{(u,v,r,p) \in G}\{\max\{r,dp(v)-p\}\}\)。
图中有环,不好处理。先考虑不断将所有出度为 \(0\) 的点删掉,这个可以在反图上用拓扑排序解决。然后我们得到的图满足,只要有足够的钱,在任意节点都可以无限走下去。找出 \(r\) 最大的一条边 \((u,v,r,p)\),用 \(r\) 去更新 \(dp(u)\),并把这条边删去。此时可能会出现出度为 \(0\) 的点,继续在反图上拓扑排序更新 \(dp\)。
复杂度除排序外 \(O(n+m)\)。
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,m,deg[200005],dp[200005];
vector <array<int,3> > g[200005];
array<int,4> es[200005];
queue <int> q;
void topo()
{
while(q.size())
{
int u=q.front();
q.pop();
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i][0],r=g[u][i][1],p=g[u][i][2];
dp[v]=min(dp[v],max(r,dp[u]-p));
deg[v]--;
if(!deg[v]) q.push(v);
}
}
}
bool cmp(array<int,4> x,array<int,4> y)
{
return x[2]>y[2];
}
void solve()
{
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>es[i][0]>>es[i][1]>>es[i][2]>>es[i][3];
sort(es+1,es+1+m,cmp);
for(int i=m;i>=1;i--) g[es[i][1]].pb({es[i][0],es[i][2],es[i][3]}),deg[es[i][0]]++;
for(int i=1;i<=n;i++) if(!deg[i]) q.push(i);
memset(dp,0x3f,sizeof(dp));
topo();
for(int i=1;i<=m;i++)
{
int u=es[i][0],v=es[i][1],p=es[i][2],w=es[i][3];
if(!deg[v]) continue;
g[v].pop_back();
// cout<<u<<" "<<v<<" "<<p<<" "<<w<<"\n";
dp[u]=min(dp[u],p);
deg[u]--;
if(!deg[u]) q.push(u);
topo();
}
for(int i=1;i<=n;i++) cout<<(dp[i]>1000000000?-1:dp[i])<<" ";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【CCO 2023】Real Mountains
称一个数在山谷里,当且仅当在左边和右边都有严格比它大的数。
观察这样一个过程,将所有在山谷里的数的最小值整体 \(+1\)。
每次操作 \((i,j,k)\),\(h_j\) 带来的代价是固定的,我们需要尽可能最小化 \(h_i,h_k\) 带来的代价。先将这些数中最左边和最右边的数 \(+1\),然后操作中间的数就可以令 \(h_i,h_k=h_j+1\),非常划算。为了最小化最左边和最右边的数操作的代价,可以算出先左后右,先右后左的代价。这是一个求一段前缀/后缀中某个数后继的问题,可以选用自己喜欢的数据结构维护。
然而 \(h\) 的值域非常大,令 \(x\) 为目前的山谷里的最小值,\(y\) 为 \(x\) 在所有 \(h_i\) 中的后继,将所有山谷里的最小值从 \(x\) 加到 \(y\) 的过程中,由于需要改变的数的集合不变,可以整体处理,而这个集合只会改变 \(O(n)\) 次,故复杂度是 \(O(n \log n)\) 的。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#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=1e6+3;
const int inf=0x3f3f3f3f;
const int INF=1e18;
struct Segtree
{
int rt=1,uidx;
int val[30000005],ls[30000005],rs[30000005];
int build(int l,int r)
{
int id=++uidx;
val[id]=0;
if(l==r) return id;
int mid=(l+r)>>1;
ls[id]=build(l,mid),rs[id]=build(mid+1,r);
return id;
}
int update(int id,int l,int r,int x,int d)
{
int u=++uidx;
val[u]=val[id],ls[u]=ls[id],rs[u]=rs[id];
if(l==r)
{
val[u]+=d;
return u;
}
int mid=(l+r)>>1;
if(x<=mid) ls[u]=update(ls[id],l,mid,x,d);
else rs[u]=update(rs[id],mid+1,r,x,d);
val[u]=val[ls[u]]+val[rs[u]];
return u;
}
int find_next(int id,int l,int r,int x)
{
if(!val[id]) return -1;
if(l==r)
{
if(l>=x) return l;
return -1;
}
int mid=(l+r)>>1;
if(x<=mid)
{
int t=find_next(ls[id],l,mid,x);
if(t!=-1) return t;
}
return find_next(rs[id],mid+1,r,x);
}
}st1,st2;
int n;
int calc(int l,int r)
{
return 1LL*(1LL*(l+r)*(r-l+1)/2)%mod;
}
int h[1000005],R1[1000005],R2[1000005],b[1000005],pmax[1000005],smax[1000005];
pii c[1000005];
void solve()
{
cin>>n;
int m;
for(int i=1;i<=n;i++) cin>>h[i],b[i]=h[i];
sort(b+1,b+1+n),m=unique(b+1,b+1+n)-b-1;
R1[0]=R2[n+1]=1;
st1.build(1,m),st2.build(1,m);
for(int i=1;i<=n;i++) h[i]=lower_bound(b+1,b+1+m,h[i])-b,R1[i]=st1.update(R1[i-1],1,m,h[i],1),pmax[i]=max(pmax[i-1],h[i]),c[i]=mp(h[i],i);
for(int i=n;i>=1;i--) R2[i]=st2.update(R2[i+1],1,m,h[i],1),smax[i]=max(smax[i+1],h[i]);
ll ans=0;
sort(c+1,c+1+n);
set <int> S;
for(int s=1,j=1;s<=m;s++)
{
while(c[j].fi==s) S.insert(c[j].se),j++;
while(S.size())
{
int u=(*S.begin());
if(s>=min(pmax[u-1],smax[u+1])) S.erase(u);
else break;
}
while(S.size())
{
int u=(*prev(S.end()));
if(s>=min(pmax[u-1],smax[u+1])) S.erase(u);
else break;
}
// cout<<s<<" "<<S.size()<<"\n";
if(S.size()==1)
{
int u=*S.begin();
int t1=st1.find_next(R1[u-1],1,m,s+1),t2=st2.find_next(R2[u+1],1,m,s+1);
// cout<<u<<" "<<b[t1]<<" "<<b[t2]<<"\n";
ans+=1LL*(b[s+1]-b[s])%mod*(b[t1]+b[t2])%mod,ans%=mod;
ans+=1LL*calc(b[s],b[s+1]-1),ans%=mod;
}
if(S.size()>=2)
{
ans+=1LL*calc(b[s],b[s+1]-1)*(ll)(S.size())%mod,ans%=mod;
// cout<<S.size()<<" "<<b[s]<<" "<<b[s+1]-1<<" "<<ans<<"\n";
int coef=2*S.size()-3;
ans+=1LL*coef*calc(b[s]+1,b[s+1])%mod,ans%=mod;
// cout<<ans<<"\n";
int u=(*S.begin()),v=(*prev(S.end()));
int t1=st1.find_next(R1[u-1],1,m,s+1),t2=st2.find_next(R2[u+1],1,m,s+1),t3=st2.find_next(R2[v+1],1,m,s+1);
int tt1=st1.find_next(R1[u-1],1,m,s+1),tt2=st1.find_next(R1[v-1],1,m,s+1),tt3=st2.find_next(R2[v+1],1,m,s+1);
// cout<<u<<" "<<v<<" "<<b[t1]<<" "<<b[t2]<<" "<<b[t3]<<" "<<b[tt1]<<" "<<b[tt2]<<" "<<b[tt3]<<"\n";
ll r=min(1LL*b[t1]+1LL*b[t2]+1LL*b[t3],1LL*b[tt1]+1LL*b[tt2]+1LL*b[tt3])%mod;
r=1LL*r*(b[s+1]-b[s])%mod;
ans+=r,ans%=mod;
}
// cout<<ans<<"\n";
}
cout<<ans<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【CCO 2023】Line Town
部分分在暗示做法。
最关键的一个性质:考虑第 \(i\) 个数 \(h_i\) 在最后被换到了 \(p_i\) 的位置,那么最终状态下,第 \(p_i\) 个数是 \((-1)^{i-p_i}h_i\),且操作次数是 \(p_i\) 的逆序对数。
先考虑 \(|h_i| \neq |h_j|\) 的部分分,按绝对值从大往小枚举,当前枚举到的数,要么丢到末尾且符号为正,要么丢到开头且符号为负。令 \(dp(i,0/1)\) 表示,已经枚举了前 \(i\) 个数,开头的数模 \(2\) 是什么。转移只需要用 BIT 优化一下计算逆序对。
再考虑 \(|h_i| \le 1\),由于符号翻转很烦,需要一点点的转化去掉它。令初始状态为 \(a_1,\cdots,a_n\),目标状态为 \(b_1,\cdots,b_n\),对于所有奇数(偶数也行)的 \(i\),将 \(a_i,b_i\) 取相反数,操作就变成了“交换相邻数,没有符号翻转”(理由:转化前后对于符号的要求均为,若 \(|i-p_i|\) 为偶数,需要 \(a_i,b_{p_i}\) 符号相同;若 \(|i-p_i|\) 为奇数,需要 \(a_i,b_{p_i}\) 符号相反)。
在转化后的问题上,需要对于一个 \(-1,0,1\) 的序列求出,变成某个序列(中间一段为 \(0\),左右两段 \(+1,-1\) 交替排列)所需要的最小交换次数。
先做单组询问:给最终状态用 \([1,n]\) 顺次标号,给初始状态中的标号满足:对于所有 \(x,y\),第 \(x\) 个数 \(y\) 在两个状态中的标号相同。操作次数就是初始状态标号的逆序对。
对于多组询问需要一点分讨,一个稍微简单的做法是,分 \(0\) 左边的数有奇数还是偶数两种情况。对于同种情况,将 \(0\) 的区间往右移动两步后,标号序列的变化也仅仅是将两个数的标号移到中间一段 \(0\) 的前面,很容易用 BIT 计算逆序对的变化量。
考虑满分做法:\(dp\) 状态一样,按绝对值 \(x\) 从大到小枚举的时候,将 \(+x\) 看成 \(+1\),\(-x\) 看成 \(-1\),绝对值小于 \(x\) 的看成 \(0\)。有时候 \(0\) 会很多,但是计算逆序对的时候只需要分别计算:\((-1,0),(1,0),(-1,1)\) 之间的贡献,额外用 BIT 维护 \(0\) 的位置即可。
复杂度 \(O(n \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;
int n,a[500005],b[500005],P[500005],Q[500005];
struct BIT
{
int t[500005];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int d)
{
for(int i=x;i<=n;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;
}
}bt;
pii calc(vector <int> vec,int tp)
{
pii res=mp(INF,INF);
vector <int> v[2];
int c0=bt.query(n);
for(int i=0;i<vec.size();i++)
{
int flg=(a[vec[i]]>0?1:0);
v[flg].pb(vec[i]);
// cout<<"flg: "<<flg<<" "<<vec[i]<<"\n";
}
int m=c0+vec.size();
vector <int> seq;
seq.clear();
for(int i=m,j=(int)(v[1].size())-1,k=(int)(v[0].size())-1;i>c0;i--)
{
int flg=(i%2==tp?0:1);
if(flg==1&&j>=0) seq.pb(v[flg][j]),j--;
if(flg==0&&k>=0) seq.pb(v[flg][k]),k--;
}
if(seq.size()==v[0].size()+v[1].size())
{
reverse(seq.begin(),seq.end());
int nw=0;
for(int i=0;i<seq.size();i++) nw+=bt.query(n)-bt.query(seq[i]),bt.update(seq[i],1);
res.fi=min(res.fi,nw);
for(int i=0;i<seq.size();i++) bt.update(seq[i],-1);
for(int i=0;i+1<seq.size();i+=2)
{
nw-=bt.query(n)-2*bt.query(seq[i]);
nw-=bt.query(n)-2*bt.query(seq[i+1]);
if(c0%2==0)
{
int tmp=(seq[i]>seq[i+1]);
if(tmp) nw--;
else nw++;
}
res.fi=min(res.fi,nw);
}
}
if(!v[tp].size()) return res;
seq.clear();
int nw=bt.query(v[tp][0]);
int del=0;
for(int i=0;i<v[tp^1].size();i++) if(v[tp^1][i]<v[tp][0]) del++;
for(int i=m,j=(int)(v[1].size())-1,k=(int)(v[0].size())-1;i>c0+1;i--)
{
int flg=(i%2==tp?0:1);
if(flg==1&&j>=(int)(tp==1)) seq.pb(v[flg][j]),j--;
if(flg==0&&k>=(int)(tp==0)) seq.pb(v[flg][k]),k--;
}
if(seq.size()==v[0].size()+v[1].size()-1)
{
reverse(seq.begin(),seq.end());
for(int i=0;i<seq.size();i++) nw+=bt.query(n)-bt.query(seq[i]),bt.update(seq[i],1);
res.se=min(res.se,nw);
for(int i=0;i<seq.size();i++) bt.update(seq[i],-1);
for(int i=0;i+1<seq.size();i+=2)
{
nw-=bt.query(n)-2*bt.query(seq[i]);
nw-=bt.query(n)-2*bt.query(seq[i+1]);
if(c0%2==0)
{
int tmp=(seq[i]>seq[i+1]);
if(tmp) nw--;
else nw++;
}
res.se=min(res.se,nw);
}
}
res.se+=del;
return res;
}
int dp[500005][2];
void solve()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(i%2==0) a[i]*=-1;
}
memset(dp,0x3f,sizeof(dp));
vector <pii > vec;
vec.clear();
for(int i=1;i<=n;i++) vec.pb(mp(abs(a[i]),i));
sort(vec.begin(),vec.end()),reverse(vec.begin(),vec.end());
dp[0][0]=0;
int j=0;
for(int i=1;i<=n;i++) bt.update(i,1);
for(int i=0;i<n;i++)
{
int l=i;
while(l<n&&vec[l].fi==vec[i].fi) l++;
l--;
if(vec[l].fi==0) break;
vector <int> nw;
nw.clear();
for(int _=i;_<=l;_++) nw.pb(vec[_].se),bt.update(vec[_].se,-1);
sort(nw.begin(),nw.end());
for(int tp=0;tp<2;tp++) if(dp[j][tp]<1e12)
{
pii tmp=calc(nw,tp);
// cout<<j<<" "<<tp<<" "<<dp[j][tp]<<"\n";
// for(int _=0;_<nw.size();_++) cout<<nw[_]<<" ";
// cout<<"\n";
// cout<<tp<<" "<<tmp.fi<<" "<<tmp.se<<"\n";
// system("pause");
dp[j+1][tp]=min(dp[j+1][tp],dp[j][tp]+tmp.fi);
dp[j+1][tp^1]=min(dp[j+1][tp^1],dp[j][tp]+tmp.se);
}
i=l,j++;
}
int ans=min(dp[j][0],dp[j][1]);
cout<<(ans>1e12?-1:ans);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【IzhO 2017】Hard route
令 \(u,v\) 为我们选的两个叶子,在点 \(w\) 取到所有点到 \(u,v\) 路径距离的最大值(即取到题目中 \(H\) 的最大值),令 \(x\) 为 \(u,v\) 路径上到 \(w\) 距离最小的点。
枚举 \(x\),将 \(x\) 的所有子树(注意定根后不要漏掉上面的那棵子树)按子树内最深叶子的深度从大到小排序。取出前三个子树中最深的叶子,令深度为 \(a,b,c(a \le b \le c)\),最大化 \(hardness\) 一定是令 \(u=a,v=b,w=c\),此时 \(hardness=c(a+b)\)(理由:\(hardness\) 可以写成 \(ab+ac+bc-(作为u,v的点的乘积)\),最小化减去的部分即可)。
计算方案数有点麻烦,先换根 dp 预处理出每个节点子树内和子树外,距离最远的叶子和叶子个数。计数的时候数出 \(a,b,c\) 有多少个可选方案,分 \(a,b\) 是否相等,\(b,c\) 是否相等讨论。
幸运的是,计算取到最大值的方案时不会被重复计算。证明只需要证对于任意取到最大值的 \(u,v\),只有一个合法的 \(w\)。若有多个,则将 \(u,v\) 其中之一替换成另一个 \(w\),能变的更优。
复杂度 \(O(n)\) 或 \(O(n \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;
vector <int> g[500005];
int rt;
int n;
pii dep1[500005],dep2[500005];
void upd(pii &x,pii y)
{
if(!y.se) return;
if(y.fi==x.fi) x.se+=y.se;
else if(y.fi>x.fi) x=y;
}
pii ans=mp(0,0);
void dfs1(int u,int fa)
{
dep1[u]=mp(1,1);
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa) continue;
dfs1(v,u),upd(dep1[u],mp(dep1[v].fi+1,dep1[v].se));
}
}
void dfs2(int u,int fa)
{
int max1=0,max2=0;
max1=dep2[u].fi+1;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa) continue;
int t=dep1[v].fi+1;
if(t>max1) max2=max1,max1=t;
else if(t>max2) max2=t;
}
int cnt1=0,cnt2=0;
if(dep2[u].fi+1==max1) cnt1+=dep2[u].se;
if(dep2[u].fi+1==max2) cnt2+=dep2[u].se;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa) continue;
int t=dep1[v].fi+1;
if(t==max1) cnt1+=dep1[v].se;
if(t==max2) cnt2+=dep1[v].se;
}
// cout<<u<<" "<<" "<<max1<<" "<<max2<<" "<<cnt1<<" "<<cnt2<<"\n";
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa) continue;
int t=dep1[v].fi+1;
if(t==max1) cnt1-=dep1[v].se;
if(t==max2) cnt2-=dep1[v].se;
if(cnt1) dep2[v]=mp(max1,cnt1);
else dep2[v]=mp(max2,cnt2);
dfs2(v,u);
if(t==max1) cnt1+=dep1[v].se;
if(t==max2) cnt2+=dep1[v].se;
}
}
void dfs3(int u,int fa)
{
vector <pii > vec;
if(u!=rt) vec.pb(dep2[u]);
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa) continue;
vec.pb(dep1[v]);
}
sort(vec.begin(),vec.end()),reverse(vec.begin(),vec.end());
if(vec.size()>=3)
{
int x=vec[0].fi,y=vec[1].fi,z=vec[2].fi;
if(y==z)
{
int cnt=0,ws=0;
if(dep2[u].fi==y) cnt=dep2[u].se;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa||dep1[v].fi!=y) continue;
// cout<<v<<" "<<dep1[v].se<<" "<<cnt<<" "<<"ok\n";
ws+=dep1[v].se*cnt,cnt+=dep1[v].se;
}
// cout<<u<<" "<<x<<" "<<y<<" "<<z<<" "<<ws<<"\n";
upd(ans,mp(x*(y+z),ws));
}
else
{
int cnt=0,cnt1=0;
if(x==y) cnt1+=vec[0].se,cnt1+=vec[1].se;
else cnt1=vec[1].se;
if(dep2[u].fi==z) cnt=dep2[u].se;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa||dep1[v].fi!=z) continue;
cnt+=dep1[v].se;
}
// cout<<u<<" "<<x<<" "<<y<<" "<<z<<" "<<cnt*cnt1<<"\n";
upd(ans,mp(x*(y+z),cnt*cnt1));
}
}
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa) continue;
dfs3(v,u);
}
}
void solve()
{
cin>>n;
if(n==2)
{
cout<<0<<" "<<1<<"\n";
return;
}
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
g[u].pb(v),g[v].pb(u);
}
for(int i=1;i<=n;i++) if(g[i].size()>=2)
{
rt=i;
break;
}
// cout<<rt<<"\n";
dfs1(rt,-1),dep2[rt]=mp(0,1);
dfs2(rt,-1),dfs3(rt,-1);
// for(int i=1;i<=n;i++) cout<<dep1[i].fi<<" "<<dep1[i].se<<" "<<dep2[i].fi<<" "<<dep2[i].se<<"\n";
if(!ans.fi) ans.se=1;
cout<<ans.fi<<" "<<ans.se<<"\n";
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【IOI2009 中国国家队选拔】\(N^2\) 数码游戏
对于测试点 \(1\),可以爆搜,复杂度 \(O((N^2)!poly(N))\)。
对于测试点 \(2,6\),可以剪十六个小纸片玩,观察发现前若干次操作一定是转矩形的最外面一圈,类似于 UUULLLDDDRRRUUULLLDDDRRR...
的过程,当把 \(1,2,3,4\) 转到最上面的时候,剩余部分可以暴力或者用下文方法解。
考虑 bfs,设计一个估价函数,每一层只保留一些可能比较优的状态,可以试试如下几个。
- 每个数到终点的曼哈顿距离,的和/平方和/立方和/平方根和。
- 重新定义距离:\((x_1,y_1),(x_2,y_2)\) 的距离为 \((|x_2-x_1|+1)(|y_2-y_1|+1)\),算和/平方和/立方和/平方根和。
- 将距离乘上 \(0\) 到这个数的距离,算和/平方和/立方和/平方根和。
实测和/平方根和比较优秀。
这样子会出现一个现象,目前队列中估价函数最低的会在两个数中反复横跳,每次扰动一步显然是不够的。可以在模 \(2/3/4=0\) 的某一轮中删减状态。
需要调一调删除的频率和保留的状态数。
当然估价函数并不是特别合理的,比如有两个相邻但位置相反的数,空格又隔得特别远。其实这种状态是很劣的,但这几种估价都会认为这种状态很优秀。或者说多个路径交错在一起,也是特别劣的,但很难设计函数把它们区分开,欢迎各位来交流更好的估价函数。
题外话:关于多项式复杂度的构造方法,也是我的赛时做法:
- 大概思路是按照 \(1,2,\cdots,N^2\) 的顺序归位,已经归位的不去动它。
- 对于前 \(N-2\) 行的前 \(N-2\) 列,记录状态 \((ux,uy,ex,ey)\),表示目前需要归位的数和空格分别在什么位置,爆搜就行。
- 对于前 \(N-2\) 行每一行的最后两列,并不能保证上一条能跑出解,但是我们一定可以把棋盘变成下面的形状之一(
.
表示空格,?
表示没被归位的数字):
1 2 . 3
? ? ? 4
1 2 4 .
? ? 3 ?
所以记录状态时记录两个数的位置和空格的位置,也一定能得到解。
- 对于后两行,由于最后一行极有可能顺序不对,上述方法不能使用。从左往右枚举列,同时归位这一列的两个数,假设 \(N=4\),目前归位 \(9,13\),只需要在第三行令 \(13\) 和 \(9\) 挨在一起,然后转下来就行,也可以记录三个坐标爆搜。
1 2 3 4
5 6 7 8
? 13 9 ?
? ? ? .
复杂度 \(O(n^7)\)。肯定有更优的做法。
但这样构造,我只在测试点 \(9\) 得到了 \(3\) 分,其余均为 \(2\) 分。
【2022 克罗地亚国家队选拔】Mapa
直觉上特别违背信息论,因为只能传递 \(3000\) bits,而传递 \(y_i\) 已经需要 \(3000\) bits。
假设解密时知道了所有 \(x_i\),那么只需要在加密的时候按 \(x_i\) 排序后传递 \(y_i\),正好符合要求。
怎么不传递 \(x_i\) 呢?或者将给定的键值对转化成一些固定的 \(x_i\),比如将所有 \(x_i\) 变成 \([1,n]\) 的排列。
拉格朗日插值!
由于 \(n\) 个键值对 \((x,y)\),唯一确定了一个 \(n-1\) 次多项式,将这个多项式求出来,传递 \([1,n]\) 的点值(传系数也行),解码的时候插值一下,由于插值有除法,可以在模 \(10^9+7\) 下算,需要的位数不变。
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,Q,X[105],Y[105];
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 F(int x)
{
int res=0;
for(int i=1;i<=N;i++)
{
int coef=1;
for(int j=1;j<=N;j++) if(j!=i) coef=coef*((x-X[j]+mod)%mod)%mod*fpow((X[i]-X[j]+mod)%mod,mod-2)%mod;
res=(res+coef*Y[i])%mod;
}
return res;
}
string obuf="";
int read(int len)
{
int res=0;
for(int i=0;i<len;i++)
{
char x;
cin>>x;
if(x=='1') res+=(1<<i);
}
return res;
}
void print(int x,int len)
{
for(int i=0;i<len;i++)
{
if(x&(1<<i)) obuf+='1';
else obuf+='0';
}
}
void enc()
{
cin>>N;
for(int i=1;i<=N;i++) cin>>X[i]>>Y[i];
for(int i=1;i<=N;i++) print(F(i),30);
cout<<obuf.size()<<"\n"<<obuf<<"\n";
}
void dec()
{
string del="";
int dirt;
string en;
cin>>N>>Q>>dirt;
for(int i=1;i<=N;i++) X[i]=i,Y[i]=read(30);
while(Q--)
{
int x;
cin>>x;
cout<<F(x)<<"\n";
}
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
cin>>_;
if(_==1) enc();
else dec();
return 0;
}
【KOI 2023】出租车
一个非常直观的结论:换乘一定是从 \(B_i\) 大的换到 \(B_i\) 小的,也就是说经过的所有点(除了终点),\(B_i\) 是递减的。
将所有 \(i\) 按 \(B_i\) 从大到小排序,令 \(dp(u)\) 表示到达点 \(u\) 所需的最小代价,转移:\(dp(u)=\min_{v|B_v \gt B_u}\{ dp(v)+A_v+B_v \cdot dist(u,v)\}\)。
将 \(dp(v)+A_v+B_v\cdot dist(u,v)\) 中,\(B_v\) 看成斜率,\(A_v+dp(v)\) 看成截距,\(dist(u,v)\) 看成自变量 \(x\)。考虑点分树,在点分树上的所有点上维护一个凸包,计算完 \(dp(v)\) 之后,在点分树上所有 \(v\) 的祖先 \(fa\) 上的凸包,加入直线 \(y=B_vx+(B_v \cdot dist(v,fa)+A_v+dp(v))\),因为 \(B\) 是递减的,用单调栈维护凸包,查询时二分查询 \(x=dist(u,fa)\) 处的 \(y\),复杂度 \(O(n \log^2 n)\)。
有个小细节,路径上经过的所有点中,终点不保证是 \(B_i\) 递减的,需要额外计算最后一步,可以用一样的方法计算。
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[100005],B[100005];
int ord[100005];
bool cmp(int x,int y)
{
return B[x]>B[y];
}
vector <pii > g[100005];
struct LCA
{
int dep[200005],dfn[200005],times;
int dist[200005];
int st[20][400005],Lg[400005];
void dfs(int u,int fa)
{
dfn[u]=++times,st[0][times]=u;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i].fi;
if(v==fa) continue;
dep[v]=dep[u]+1,dist[v]=dist[u]+1LL*g[u][i].se,dfs(v,u);
st[0][++times]=u;
}
}
int mindep(int x,int y)
{
if(dep[x]<dep[y]) return x;
return y;
}
void build()
{
times=0,dep[1]=dist[1]=0;
dfs(1,-1);
Lg[1]=0,Lg[2]=1;
for(int i=3;i<=times;i++) Lg[i]=Lg[i/2]+1;
for(int k=1;k<20;k++) for(int i=1;i+(1<<k)-1<=times;i++)
st[k][i]=mindep(st[k-1][i],st[k-1][i+(1<<(k-1))]);
}
int getlca(int u,int v)
{
u=dfn[u],v=dfn[v];
if(u>v) swap(u,v);
int s=Lg[v-u+1];
return mindep(st[s][u],st[s][v-(1<<s)+1]);
}
int getdis(int u,int v)
{
int l=getlca(u,v);
return dist[u]+dist[v]-2LL*dist[l];
}
}Lca;
int sz[100005],maxsz[100005],rt=0,tot;
vector <int> h[100005];
int vis[100005],par[100005];
void dfs0(int u,int fa)
{
sz[u]=1;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i].fi;
if(vis[v]||v==fa) continue;
dfs0(v,u),sz[u]+=sz[v];
}
}
void dfs1(int u,int fa)
{
maxsz[u]=0;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i].fi;
if(vis[v]||v==fa) continue;
maxsz[u]=max(maxsz[u],sz[v]),dfs1(v,u);
}
maxsz[u]=max(maxsz[u],tot-sz[u]);
if(maxsz[u]<maxsz[rt]) rt=u;
}
void calc(int u)
{
vis[u]=1;
vector <pii > ss;
ss.clear();
dfs0(u,-1);
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i].fi;
if(vis[v]) continue;
ss.pb(mp(v,sz[v]));
}
for(int i=0;i<ss.size();i++)
{
rt=0,tot=ss[i].se;
dfs1(ss[i].fi,-1);
par[rt]=u;
calc(rt);
}
}
int dp[100005];
vector <pii > stk[100005];
vector <double > pnt[100005];
double get(pii x,pii y)
{
if(x.fi==y.fi) return (x.se>y.se?-1e18:1e18);
return 1.0*(y.se-x.se)/(x.fi-y.fi);
}
void ins(int id,pii li)
{
li.fi*=-1,li.se*=-1;
if(!stk[id].size()) stk[id].pb(li),pnt[id].pb(-1e18);
else
{
while(stk[id].size()>1&&get(li,stk[id].back())<pnt[id].back()) stk[id].pop_back(),pnt[id].pop_back();
pnt[id].pb(get(li,stk[id].back())),stk[id].pb(li);
}
}
int query(int x,int y)
{
if(!stk[x].size()) return INF;
int pos=upper_bound(pnt[x].begin(),pnt[x].end(),(double)(y))-pnt[x].begin()-1;
int res=y*stk[x][pos].fi+stk[x][pos].se;
return -res;
}
void trans(int id)
{
dp[id]=min(dp[id],A[1]+Lca.getdis(1,id)*B[1]);
int u=id;
while(u) dp[id]=min(dp[id],query(u,Lca.getdis(id,u))),u=par[u];
u=id;
while(u) ins(u,mp(B[id],dp[id]+Lca.getdis(id,u)*B[id]+A[id])),u=par[u];
}
void trans2(int id)
{
dp[id]=min(dp[id],A[1]+Lca.getdis(1,id)*B[1]);
int u=id;
while(u) dp[id]=min(dp[id],query(u,Lca.getdis(id,u))),u=par[u];
}
vector <int> travel(vector <int> _A,vector <signed> _B,vector <signed> _U,vector <signed> _V,vector <signed> _W)
{
n=_A.size();
for(int i=1;i<=n;i++) A[i]=_A[i-1],B[i]=_B[i-1];
for(int i=0;i<_U.size();i++)
{
int u=_U[i],v=_V[i],w=_W[i];
u++,v++;
g[u].pb(mp(v,w)),g[v].pb(mp(u,w));
}
Lca.build();
maxsz[0]=tot=n;
dfs0(1,-1),dfs1(1,-1),calc(rt);
memset(dp,0x3f,sizeof(dp));
dp[1]=0;
for(int i=1;i<=n;i++) ord[i]=i;
sort(ord+1,ord+1+n,cmp);
for(int i=1;i<=n;i++) if(ord[i]!=1) trans(ord[i]);
for(int i=1;i<=n;i++) if(ord[i]!=1) trans2(ord[i]);
vector <int> ret;
for(int i=2;i<=n;i++) ret.pb(dp[i]);
return ret;
}
【UOI 2023】乌克兰
非常直观的感觉,操作次数不会特别的多,考虑证明上界为 \(3\)。
令 \(a_i\) 为给定的数组,\(s_i\) 为 \(a_i\) 的前缀和,令 \(s_x,s_y\) 分别为 \(s_i\) 的最小值和最大值。
- 若 \(x \gt y\),则用操作 \(1\) 令 \(x \lt y\)。
- 不妨令 \(s_x \lt 0,s_y \gt 0\),先用操作 \(2\) 操作 \([x+1,n]\),被操作的所有数 \(u\) 会变成 \(s_u-s_x\),是非负的,因为 \(s_x \lt 0\),故最大值的位置仍然在 \([x+1,n]\) 中,令 \(y\) 为新的最大值出现位置,用操作 \(3\) 操作 \([1,y]\),被操作的所有数 \(v\) 会变成 \(s_y-s_{v-1}\),同样非负。
分情况讨论:
若答案为 \(0\),当且仅当所有 \(a_i \ge 0\)。
若答案为 \(1\),不妨要么进行一次 \(1\) 操作,要么存在一个区间满足前/后缀和 \(\ge 0\),且区间外的所有数 \(\ge 0\)。而在区间左右两端加上非负数是不劣的,故只要判断前/后缀和数组是否都 \(\ge 0\)。
若答案为 \(3\),直接构造。
若答案为 \(2\),分两种操作类型相同/不同讨论。
若相同,令两次都为 \(2\) 操作,我们需要找到一个区间,满足将这个区间替换成前缀数组后,整个序列的前缀数组均 \(\ge 0\)。令第一步操作的区间为 \([l,r]\),则 \([1,l-1]\) 的前缀数组需要 \(\ge 0\),令 \(l=1\),是不劣的,故第一次操作一定是操作一个前缀。枚举这个前缀,需要维护:单点修改,求前缀和数组最小值。用线段树维护前缀和数组,单点修改对应了区间加减,查询对应了全局最小值。
若不同,不妨令第一次为 \(3\) 操作,第二次为 \(2\) 操作,考虑分治,令当前区间为 \([l,r]\),中点为 \(mid\),令 \(a_i\) 为原数组,\(s_i\) 为前缀和。
对于所有 \(i \in [mid+1,r]\) 计算:
- \(A_i\):若第一次操作的右端点为 \(i\),则操作完后 \(a_{mid+1}=A_i\)。计算是容易的。
- \(B_i\):若右端点为 \(i\),第一次操作完之后,\([1,mid]\) 的和至少是多少才能保证对于所有 \(j \in [mid+1,n]\),有 \(s_j \ge 0\)。\(B_i\) 由两部分:\([mid+1,r],[r+1,n]\) 确定,\([r+1,n]\) 是容易计算的,预处理原数组中 \(s_i\) 的后缀最小值,计算 \([mid+1,r]\) 中后缀和的和即可。\([mid+1,r]\) 有点麻烦,考虑一个位置 \(j\),第一次操作完之后,\([mid+1,j]\) 中的和 \(t_j\) 会随着 \(r\) 的增加发生什么变化:令新加入的数为 \(a_r\),则对于所有 \(j \in [mid+1,r-1]\),\(t_j\) 会增加 \((j-mid)\cdot t_j\)。将所有 \(j\) 看成一条直线 \(y=(j-mid) \cdot x+b\),计算 \(B_i\) 可以看做查询凸包上 \(x=s_i-s_{mid}\) 处的 \(y\) 值。可以用李超树或单调栈维护。
对于所有 \(i \in [l,mid]\) 计算:
- \(D_i\):原序列中 \([i,m]\) 后缀和之和。
- \(E_i\):第一次操作完之后,若左端点为 \(i\),则 \(a_{mid+1}\) 至少为多少才能保证 \([1,mid]\) 中所有前缀和非负。同样的考虑将 \(l\) 减少 \(1\) 之后,所有位置 \(j\) 的前缀和 \(t_j\) 会发生什么变化,这个也可以写成一条直线 \(y=(j-i+1)\cdot x+b\),我们需要找到一个最小的 \(x\) 使得对于所有直线均有 \(y \ge 0\),即 \((j+1)x+b \ge i \cdot x\),可以看做求直线 \(y=i \cdot x\) 和凸包的交点,用单调栈+二分维护,如果不介意多个 \(\log\) 的话也可以用李超树维护+二分,也能擦着时限过。
- \(F_i\):原序列中 \([1,i-1]\) 的和。
一个合法的第一次操作区间 \([L,R]\) 需要满足:
- \([1,L-1]\) 的前缀和数组均 \(\ge 0\),很好判断。
- \(A_R \ge E_L\)
- \(D_L+F_L+A_R(mid-L+1) \ge B_R\)。
三个限制条件分别令 \([1,L-1],[L,mid],[mid+1,n]\) 三段满足条件。将所有满足第一条限制的 \(L\),和所有 \(R\) 搞出来,按 \(A_R\) 或 \(E_L\) 排序。第三个限制,对于所有 \(L\) 可以看成直线 \(y=(mid-L+1) \cdot x+(D_L+F_L)\),对于所有 \(R\) 可以看做查询 \(x=A_R\)。
复杂度 \(O(n \log^2 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 n,dirt;
struct Line
{
int k,b;
};
struct SegTree
{
int t[800015],tag[800015];
void pushdown(int id)
{
if(tag[id])
{
t[id<<1]+=tag[id],t[id<<1|1]+=tag[id];
tag[id<<1]+=tag[id],tag[id<<1|1]+=tag[id];
tag[id]=0;
}
}
void update_add(int id,int l,int r,int x,int y,int d)
{
if(x<=l&&r<=y)
{
tag[id]+=d,t[id]+=d;
return;
}
int mid=(l+r)>>1;
pushdown(id);
if(x<=mid) update_add(id<<1,l,mid,x,y,d);
if(y>mid) update_add(id<<1|1,mid+1,r,x,y,d);
t[id]=min(t[id<<1],t[id<<1|1]);
}
void update(int i,int x,int y,int tp)
{
if(tp==0)
{
update_add(1,1,n,i,n,-x);
update_add(1,1,n,i,n,y);
}
else
{
update_add(1,1,n,1,i,-x);
update_add(1,1,n,1,i,y);
}
}
}st;
struct Lichao
{
Line p[2000005];
int t[5000005];
int ls[5000005],rs[5000005];
int idx,tid;
void init()
{
idx=0,tid=1;
ls[1]=0,rs[1]=0,t[1]=0;
p[0].k=1,p[0].b=-INF;
}
int add_line(int k,int b)
{
p[++idx].k=k,p[idx].b=b;
return idx;
}
int get_y(int id,int x)
{
return p[id].k*x+p[id].b;
}
void update(int &id,int l,int r,int lid)
{
if(!id) id=++tid,t[id]=0,ls[id]=rs[id]=0;
int mid=(l+1000000+r+1000000)/2-1000000;
if(get_y(lid,mid)>get_y(t[id],mid)) swap(lid,t[id]);
if(get_y(lid,l)>get_y(t[id],l)) update(ls[id],l,mid,lid);
if(get_y(lid,r)>get_y(t[id],r)) update(rs[id],mid+1,r,lid);
}
void insert(int &id,int l,int r,int x,int y,int lid)
{
if(!id) id=++tid,t[id]=0,ls[id]=rs[id]=0;
if(x<=l&&r<=y)
{
update(id,l,r,lid);
return;
}
int mid=(l+1000000+r+1000000)/2-1000000;
if(x<=mid) insert(ls[id],l,mid,x,y,lid);
if(y>mid) insert(rs[id],mid+1,r,x,y,lid);
}
int query(int id,int l,int r,int x)
{
assert(l<=x&&x<=r);
if(!id) return -INF;
int mid=(l+r)/2;
int res=get_y(t[id],x);
if(l==r) return res;
if(x<=mid) res=max(res,query(ls[id],l,mid,x));
else res=max(res,query(rs[id],mid+1,r,x));
return res;
}
}lt;
int A[200005];
int a[200005],b[200005],c[200005],d[200005],e[200005],f[200005],ps[200005],ss[200005],pspm[200005],pssm[200005];
vector <pii > stk;
vector <double > pnt;
double get(pii x,pii y)
{
if(x.fi==y.fi) return (x.se>y.se?-1e18:1e18);
return 1.0*(y.se-x.se)/(x.fi-y.fi);
}
void ins(pii li)
{
if(!stk.size()) stk.pb(li),pnt.pb(-1e18);
else
{
while(stk.size()>1&&get(li,stk.back())<pnt.back()) stk.pop_back(),pnt.pop_back();
pnt.pb(get(li,stk.back())),stk.pb(li);
}
}
double inter(pii l1,pii l2)
{
return 1.0*(l2.se-l1.se)/(l1.fi-l2.fi);
}
int query(pii li)
{
if(!stk.size()) return INF;
pnt.pb(1e18);
int L=0,R=pnt.size()-2,res=0;
while(L<=R)
{
int mid=(L+R)>>1;
double tmp=inter(stk[mid],li);
if(pnt[mid]<=tmp&&tmp<=pnt[mid+1])
{
res=mid;
break;
}
if(tmp>pnt[mid+1]) L=mid+1;
else R=mid-1;
}
pnt.pop_back();
return ceil(inter(stk[res],li));
}
pii divide(int l,int r)
{
if(l>=r) return mp(-1,-1);
int mid=(l+r)>>1;
for(int i=mid+1,s=0;i<=r;i++) s+=A[i],a[i]=s;
lt.init();
int rt=1;
for(int i=mid+1,s=0,sum=0;i<=r;i++)
{
s+=(i-mid)*A[i];
sum+=A[i];
int K=i-mid,B=s;
B-=K*sum;
int lid=lt.add_line(-K,-B);
lt.insert(rt,-n,n,-n,n,lid);
b[i]=lt.query(rt,-n,n,sum);
b[i]=max(b[i],-(pssm[i+1]-ps[i]+s));
}
for(int i=mid,s=0,sum=0;i>=l;i--)
{
sum+=A[i],s+=sum;
d[i]=s;
}
lt.init();
stk.clear(),pnt.clear();
for(int i=mid,sum=0,tag=0;i>=l;i--)
{
sum+=A[i],tag+=sum;
int K=(i+1),B=sum-tag;
ins(mp(-K,-B));
e[i]=query(mp(-i,tag+ps[i-1]));
}
for(int i=mid;i>=l;i--) f[i]=ps[i-1];
vector <array<int,4> > vec;
vec.clear();
for(int i=l;i<=mid;i++) if(pspm[i-1]>=0) vec.pb({e[i],0,i});
for(int i=mid+1;i<=r;i++) vec.pb({a[i],1,i});
sort(vec.begin(),vec.end());
lt.init();
for(int i=0;i<vec.size();i++)
{
int u=vec[i][2];
if(vec[i][1]==0)
{
int K=mid-u+1,B=d[u]+f[u];
int lid=lt.add_line(K,B);
lt.insert(rt,-n,n,-n,n,lid);
}
else
{
int tmp=lt.query(rt,-n,n,a[u]);
if(tmp>=b[u])
{
int v=-1;
for(int j=0;j<i;j++) if(vec[j][1]==0)
{
v=vec[j][2];
int K=mid-v+1,B=d[v]+f[v];
if(K*a[u]+B==tmp) break;
}
assert(v!=-1);
return mp(v,u);
}
}
}
pii tmp=divide(l,mid);
if(tmp.fi!=-1) return tmp;
tmp=divide(mid+1,r);
if(tmp.fi!=-1) return tmp;
return mp(-1,-1);
}
vector <array<int,3> > chk()
{
memset(st.t,0,sizeof(st.t));
memset(st.tag,0,sizeof(st.tag));
for(int i=1;i<=n;i++) st.update(i,0,A[i],0);
for(int i=1,s=0;i<=n;i++)
{
s+=A[i];
st.update(i,A[i],s,0);
if(st.t[1]>=0)
{
vector <array<int,3> > vec;
vec.pb({2,1,i});
vec.pb({2,1,n});
return vec;
}
}
memset(st.t,0,sizeof(st.t));
memset(st.tag,0,sizeof(st.tag));
for(int i=1;i<=n;i++) st.update(i,0,A[i],1);
for(int i=n,s=0;i>=1;i--)
{
s+=A[i];
st.update(i,A[i],s,1);
if(st.t[1]>=0)
{
vector <array<int,3> > vec;
vec.pb({3,i,n});
vec.pb({3,1,n});
return vec;
}
}
memset(pssm,0x3f,sizeof(pssm)),memset(pspm,0x3f,sizeof(pspm));
for(int i=1;i<=n;i++) ps[i]=ps[i-1]+A[i],pspm[i]=pssm[i]=ps[i];
for(int i=1;i<=n;i++) ss[i]=ss[i+1]+A[i];
for(int i=2;i<=n;i++) pspm[i]=min(pspm[i-1],pspm[i]);
for(int i=n-1;i>=1;i--) pssm[i]=min(pssm[i+1],pssm[i]);
pii tmp=divide(1,n);
vector <array<int,3> > vec;
for(int i=0;i<100;i++) vec.pb({-1,-1,-1});
if(tmp.fi==-1) return vec;
vec.clear();
vec.pb({3,tmp.fi,tmp.se}),vec.pb({2,1,n});
return vec;
}
void solve()
{
cin>>n>>dirt;
for(int i=1;i<=n;i++) cin>>A[i];
bool ok=1;
for(int i=1;i<=n;i++) ok&=(A[i]>=0);
if(ok)
{
cout<<"0\n";
return;
}
ok=1;
for(int i=1,s=0;i<=n;i++) s+=A[i],ok&=(s>=0);
if(ok)
{
cout<<"1\n";
cout<<2<<" "<<1<<" "<<n<<"\n";
return;
}
ok=1;
for(int i=n,s=0;i>=1;i--) s+=A[i],ok&=(s>=0);
if(ok)
{
cout<<"1\n";
cout<<3<<" "<<1<<" "<<n<<"\n";
return;
}
for(int i=1;i<=n;i++) A[i]*=-1;
ok=1;
for(int i=1;i<=n;i++) ok&=(A[i]>=0);
if(ok)
{
cout<<"1\n1\n";
return;
}
ok=1;
for(int i=1,s=0;i<=n;i++) s+=A[i],ok&=(s>=0);
if(ok)
{
cout<<"2\n1\n";
cout<<2<<" "<<1<<" "<<n<<"\n";
return;
}
ok=1;
for(int i=n,s=0;i>=1;i--) s+=A[i],ok&=(s>=0);
if(ok)
{
cout<<"2\n1\n";
cout<<3<<" "<<1<<" "<<n<<"\n";
return;
}
for(int i=1;i<=n;i++) A[i]*=-1;
vector <array<int,3> > v1=chk();
reverse(A+1,A+1+n);
vector <array<int,3> > v2=chk();
for(int i=0;i<v2.size();i++)
{
if(v2[i][0]==2) v2[i][0]=3;
else if(v2[i][0]==3) v2[i][0]=2;
v2[i][1]=n-v2[i][1]+1,v2[i][2]=n-v2[i][2]+1;
swap(v2[i][1],v2[i][2]);
}
reverse(A+1,A+1+n);
for(int i=1;i<=n;i++) A[i]*=-1;
vector <array<int,3> > v3=chk();
v3.insert(v3.begin(),{1,-1,-1});
reverse(A+1,A+1+n);
vector <array<int,3> > v4=chk();
v4.insert(v4.begin(),{1,-1,-1});
for(int i=0;i<v4.size();i++)
{
if(v4[i][0]==2) v4[i][0]=3;
else if(v4[i][0]==3) v4[i][0]=2;
v4[i][1]=n-v4[i][1]+1,v4[i][2]=n-v4[i][2]+1;
swap(v4[i][1],v4[i][2]);
}
vector <array<int,3> > ans;
for(int i=0;i<100;i++) ans.pb({-1,-1,-1});
if(v1.size()<ans.size()) ans=v1;
if(v2.size()<ans.size()) ans=v2;
if(v3.size()<ans.size()) ans=v3;
if(v4.size()<ans.size()) ans=v4;
cout<<ans.size()<<"\n";
for(int i=0;i<ans.size();i++)
{
cout<<ans[i][0];
if(ans[i][0]!=1) cout<<" "<<ans[i][1]<<" "<<ans[i][2];
cout<<"\n";
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【2022 克罗地亚国家队选拔】喝喝粥
将每个所有人帽子颜色的状态 \(u\) 拆成两个节点 \(u_0,u_1\)。若两个状态 \(x,y\) 仅有一位 \(d\) 不同,不妨令 \(x\) 的第 \(d\) 位为 \(0\),则在 \(x_0,y_1\) 之间连一条无向边,含义是第 \(d\) 个人无法区分 \(x,y\) 这两个状态,需要给这条边定向:指向 \(x_0\) 表示猜 \(0\),指向 \(y_1\) 表示猜 \(1\)。
定完向后,对于一个度数为 \(deg\) 的节点,至少有 \(\lfloor deg/2 \rfloor\) 条边指向它。我们可以想到欧拉回路:建立一个虚点,先对于所有度数为奇数的点(一定有偶数个)向这个虚点连边,跑出欧拉路,按照欧拉路定向,每个点入度和出度相等,均为 \(\ge \lfloor deg/2 \rfloor\),符合题意。
复杂度 \(O(2^NN)\)。
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 ans[18][300005];
int del_bt(int x,int d)
{
int res=x&((1<<d)-1);
int t=x-res;
t>>=(d+1);
t<<=d;
return res+t;
}
vector <pii > g[1000005];
vector <int> pa;
int u0[300005],u1[300005],uidx,eidx,tmp[1000005],cur[1000005];
bool vis[1000005];
bool used[5000005];
void dfs(int u)
{
vis[u]=1;
while(1)
{
if(cur[u]>=g[u].size()) return;
int v=g[u][cur[u]].fi;
cur[u]++;
if(used[g[u][cur[u]-1].se]) continue;
used[g[u][cur[u]-1].se]=1;
dfs(v);
pa.pb(v);
}
}
void solve()
{
cin>>n;
for(int i=0;i<(1<<n);i++) u0[i]=++uidx,u1[i]=++uidx,tmp[u0[i]]=tmp[u1[i]]=i;
for(int i=0;i<(1<<n);i++) for(int j=0;j<n;j++)
{
int u=i,v=i^(1<<j);
if(u>v) continue;
int x=u0[u],y=u1[v];
if(u&(1<<j)) x=u1[u],y=u0[v];
// cout<<"addedge: "<<x<<" "<<y<<" "<<j<<"\n";
int id=++eidx;
g[x].pb(mp(y,id)),g[y].pb(mp(x,id));
}
for(int i=1;i<=uidx;i++) if(g[i].size()%2==1)
{
int id=++eidx;
g[0].pb(mp(i,id)),g[i].pb(mp(0,id));
}
memset(ans,-1,sizeof(ans));
for(int i=0;i<=uidx;i++) if(!vis[i])
{
pa.clear();
dfs(i);
// for(int j=0;j<pa.size();j++) cout<<pa[j]<<" ";
// cout<<"\n";
for(int j=0;j<pa.size();j++)
{
int u=pa[j],v=pa[(j+1)%pa.size()];
// cout<<u<<" --> "<<v<<"\n";
if(!u||!v) continue;
u=tmp[u],v=tmp[v];
// cout<<u<<" "<<v<<" ";
int d=-1,op=0;
for(int l=0;l<n;l++)
{
int x=0,y=0;
if(u&(1<<l)) x=1;
if(v&(1<<l)) y=1;
if(x!=y)
{
d=l,op=y;
break;
}
}
// cout<<d<<" "<<del_bt(u,d)<<" "<<op<<"\n";
ans[d][del_bt(u,d)]=op;
}
}
for(int i=n-1;i>=0;i--)
{
for(int j=0;j<((1<<(n-1)));j++) cout<<(char)(ans[i][j]+'B');
cout<<"\n";
}
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【JOI 2018 Final】毒蛇越狱
先考虑只有 0
和 ?
的情况,可以通过预处理高维前缀和解决,只有 1
和 ?
也是容易的,预处理后缀和即可。
如果 0
1
?
都存在,可以考虑对某种字符容斥:以 0
为例,先把所有 0
替换成 ?
,再减去这些位置上至少有一个位置为 1
的所有情况,枚举一个子集用容斥算算就行。
取三种字符出现次数最少的算,0
1
用容斥,?
直接暴力,复杂度 \(O(2^nn+Q2^{\lfloor n/3 \rfloor})\)。
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 w[1100005];
char s[1100005];
int pre[1100005],suf[1100005],ppc[1100005],n,q;
int ans=0;
vector <int> vec;
void dfs(int idx,int mask,int coef,int tp)
{
if(idx==vec.size())
{
if(tp==0) ans+=coef*suf[mask];
if(tp==1) ans+=coef*pre[mask];
if(tp==2) ans+=w[mask];
return;
}
dfs(idx+1,mask,coef*(tp==0?1:-1),tp);
dfs(idx+1,mask+(1<<vec[idx]),coef*(tp==1?1:-1),tp);
}
void calc0()
{
int mask=0;
for(int i=0;i<n;i++)
{
if(s[i]=='0') vec.pb(i);
if(s[i]=='1') mask+=(1<<i);
}
ans=0;
dfs(0,mask,1,0);
}
void calc1()
{
int mask=0;
for(int i=0;i<n;i++)
{
if(s[i]=='1') vec.pb(i);
if(s[i]=='?') mask+=(1<<i);
}
ans=0;
dfs(0,mask,1,1);
}
void calc2()
{
int mask=0;
for(int i=0;i<n;i++)
{
if(s[i]=='?') vec.pb(i);
if(s[i]=='1') mask+=(1<<i);
}
ans=0;
dfs(0,mask,1,2);
}
void solve()
{
cin>>n>>q;
cin>>s;
for(int i=0;i<(1<<n);i++) w[i]=pre[i]=suf[i]=s[i]-'0',ppc[i]=__builtin_popcount(i);
for(int i=0;i<n;i++) for(int j=0;j<(1<<n);j++)
{
if(j&(1<<i)) pre[j]+=pre[j^(1<<i)];
else suf[j]+=suf[j^(1<<i)];
}
while(q--)
{
cin>>s;
reverse(s,s+n);
int c0=0,c1=0,c2=0;
for(int i=0;i<n;i++)
{
if(s[i]=='0') c0++;
if(s[i]=='1') c1++;
if(s[i]=='?') c2++;
}
vec.clear();
// calc2();
if(c0<=c1&&c0<=c2) calc0();
else if(c1<=c0&&c1<=c2) calc1();
else calc2();
cout<<ans<<"\n";
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【IOI 2021】喷泉公园
把喷泉放到网格上,连一条边就相当于画上一个小正方形的一条边,然后需要给每条边选一个格子。
先给每个格子钦定一个方向(只被横向的边选或者只被纵向的边选),可以黑白染色,这样每条边都被恰好“预分配”了一个格子。
将点以行为第一关键字,列为第二关键字排序,每次去判断往右或往下是否可以加入这条边,加完之后判断连通性。
正确性蛮玄学的,大概就是如果一条边加入不了就一定形成环,画出来就大概是往右下方向递归寻找合法的边,一定可以找到。
Code
#include "parks.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
int N,fa[200005];
int find(int x)
{
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
void merge(int x,int y)
{
int xx=find(x),yy=find(y);
if(xx!=yy) fa[xx]=yy;
}
map <pii,int> m1,m2;
int construct_roads(vector <int> _x, vector <int> _y)
{
N=_x.size();
for(int i=0;i<N;i++) fa[i]=i;
vector <array<int,3> > vec;
vec.clear();
for(int i=0;i<N;i++) vec.pb({_x[i],_y[i],i}),m1[mp(_x[i],_y[i])]=i;
sort(vec.begin(),vec.end());
for(int i=0;i<N;i++)
{
if(m1.find(mp(_x[i],_y[i]+2))!=m1.end()) merge(i,m1[mp(_x[i],_y[i]+2)]);
if(m1.find(mp(_x[i]+2,_y[i]))!=m1.end()) merge(i,m1[mp(_x[i]+2,_y[i])]);
}
for(int i=1;i<N;i++) if(find(i)!=find(1)) return 0;
for(int i=0;i<N;i++) fa[i]=i;
vector <int> U,V,A,B;
for(int i=0;i<vec.size();i++)
{
int x=vec[i][0],y=vec[i][1],u=vec[i][2];
if(m1.find(mp(x,y+2))!=m1.end())
{
int v=m1[mp(x,y+2)];
pii pos=mp(x-1,y+1);
if((pos.fi/2+pos.se/2)%2==0) pos.fi+=2;
if(find(u)!=find(v)&&!m2[pos]) merge(u,v),U.pb(u),V.pb(v),A.pb(pos.fi),B.pb(pos.se),m2[pos]=1;
}
if(m1.find(mp(x+2,y))!=m1.end())
{
int v=m1[mp(x+2,y)];
pii pos=mp(x+1,y-1);
if((pos.fi/2+pos.se/2)%2==1) pos.se+=2;
if(find(u)!=find(v)&&!m2[pos]) merge(u,v),U.pb(u),V.pb(v),A.pb(pos.fi),B.pb(pos.se),m2[pos]=1;
}
}
if(U.size()<N-1) return 0;
build(U,V,A,B);
return 1;
}
【PA 2021】Od deski do deski
如何判断一个序列合法:只需要判断序列是否能被划分为若干连续段,每一段首位相同。令 \(f(i)\) 表示前 \(i\) 个数是否可行,转移从 \(f(i)\) 转移到 \(f(j)\) 的条件是 \(a_{i+1}=a_j\)。
回到原问题,令 \(dp(i,j,0/1)\) 表示,长度为 \(i\),有 \(j\) 个数 \(x\) 满足能找到一个位置 \(pos\),满足 \(a_{pos}=x\) 且 \(f(pos-1)=1\),当前整个序列是否合法,转移:
- \(dp(i,j,0) \times j \rightarrow dp(i+1,j,1)\)
- \(dp(i,j,0) \times (m-j) \rightarrow dp(i+1,j,0)\)
- \(dp(i,j,1) \times j \rightarrow dp(i+1,j,1)\)
- \(dp(i,j,1) \times (m-j) \rightarrow dp(i+1,j+1,0)\)
含义很显然。
复杂度 \(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 n,m;
int dp[3005][3005][2];
void solve()
{
cin>>n>>m;
dp[0][0][1]=1;
for(int i=0;i<n;i++)
{
for(int j=0;j<=min(i,m);j++)
{
// cout<<i<<" "<<j<<" "<<dp[i][j][0]<<" "<<dp[i][j][1]<<"...\n";
if(dp[i][j][0]) dp[i+1][j][1]=(dp[i+1][j][1]+dp[i][j][0]*j)%mod,dp[i+1][j][0]=(dp[i+1][j][0]+dp[i][j][0]*(m-j))%mod;
if(dp[i][j][1]) dp[i+1][j][1]=(dp[i+1][j][1]+dp[i][j][1]*j)%mod,dp[i+1][j+1][0]=(dp[i+1][j+1][0]+dp[i][j][1]*(m-j))%mod;
}
// for(int j=0;j<=n;j++) cout<<dp[i+1][j][0]<<" "<<dp[i+1][j][1]<<"\n";
// system("pause");
}
int ans=0;
for(int j=0;j<=min(n,m);j++) ans=(ans+dp[n][j][1])%mod;
cout<<ans<<"\n";
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【CTT 2021】经典游戏
在节点 \(u\) 处棋子的 \(sg\) 值是:令 \(v\) 为 \(u\) 子树内深度最大的一个叶子,则 \(sg(u)=dep_v-dep_u\)。
后手可以获胜当且仅当目前整体的异或和 \(\le sg(rt)\)。
维护处每个点的 \(sg\) 值是容易的。以 \(u\) 为根,令 \(f_u,g_u\) 分别表示在所有 \(u\) 的儿子 \(v\) 中,\(sg(v)\) 的最大值 \(+1\) 和次大值 \(+1\),修改时的形式大概为:任意定一个根后,将一个子树异或上 \(f_u\) 或 \(g_u\),子树外异或上另一个数,可以在 dfs 序上维护。
查询的话,暴力判断 \(u\) 和它的父亲,对于所有儿子,我们只需要对于每个节点维护关于儿子的 Trie,由于大多数修改都是整体 XOR,可以打懒标记,令当前的懒标记为 \(x\),查询的形式大概为:求 \(i\) 的个数满足 \(a_i \space \text{xor} \space x \gt b_i\)。一次修改整体只有 \(O(1)\) 个儿子的 \(a_i\) 会改变,这个可以暴力。
复杂度 \(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
const int mod=998244353;
const int inf=0x3f3f3f3f;
int n;
vector <int> g[1000005];
struct Trie
{
int rt[1000005],sum[35000005],uidx;
int son[2][35000005];
void init()
{
for(int i=1;i<=n;i++) rt[i]=++uidx;
}
// vector <array<int,3> > vec[1000005];
void update(int Rt,int a,int b,int d)
{
// vec[Rt].pb({a,b,d});
int u=Rt;
for(int i=19;i>=0;i--)
{
int ba=0,bb=0;
if(a&(1<<i)) ba=1;
if(b&(1<<i)) bb=1;
if(bb==0)
{
if(!son[ba^1][u]) son[ba^1][u]=++uidx;
sum[son[ba^1][u]]+=d;
if(!son[ba][u]) son[ba][u]=++uidx;
u=son[ba][u];
}
else
{
if(!son[ba^1][u]) son[ba^1][u]=++uidx;
u=son[ba^1][u];
}
}
}
int query(int Rt,int tag)
{
int u=Rt,res=0;
// for(int i=0;i<vec[Rt].size();i++) if((vec[Rt][i][0]^tag)>vec[Rt][i][1]) res+=vec[Rt][i][2];
// return res;
for(int i=19;i>=0;i--)
{
if(!u) break;
res+=sum[u];
int b=0;
if(tag&(1<<i)) b=1;
u=son[b][u];
}
if(u) res+=sum[u];
return res;
}
}tr;
struct BIT
{
int t[1000005];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int y,int d)
{
for(int i=x;i<=n;i+=lowbit(i)) t[i]^=d;
for(int i=y+1;i<=n;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;
}
}bt;
int dfn[1000005],dfnR[1000005],son[1000005],par[1000005],clk;
pii m1[1000005],m2[1000005];
int a0[1000005];
void upd_dfs(int u,pii x)
{
if(x.fi>m1[u].fi) m2[u]=m1[u],m1[u]=x;
else if(x.fi>m2[u].fi) m2[u]=x;
}
void dfs1(int u,int fa)
{
dfn[u]=dfnR[u]=++clk;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa) continue;
dfs1(v,u),upd_dfs(u,mp(m1[v].fi+1,v));
dfnR[u]=dfnR[v];
}
par[u]=fa,son[u]=m1[u].se;
}
pii in[10000005],out[1000005];
void dfs2(int u,int fa)
{
int max1=0,max2=0;
max1=out[u].fi+1;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa) continue;
int t=in[v].fi+2;
if(t>max1) max2=max1,max1=t;
else if(t>max2) max2=t;
}
int cnt1=0,cnt2=0;
if(out[u].fi+1==max1) cnt1=u;
if(out[u].fi+1==max2) cnt2=u;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa) continue;
int t=in[v].fi+2;
if(t==max1) cnt1=v;
if(t==max2) cnt2=v;
}
// cout<<u<<" "<<" "<<max1<<" "<<max2<<" "<<cnt1<<" "<<cnt2<<"\n";
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa) continue;
if(v!=cnt1) upd_dfs(v,mp(max1,u)),out[v]=mp(max1,u);
else upd_dfs(v,mp(max2,u)),out[v]=mp(max2,u);
dfs2(v,u);
}
}
void solve()
{
int Q;
cin>>n>>Q;
if(n==1)
{
while(Q--) cout<<0<<"\n";
return;
}
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
g[u].pb(v),g[v].pb(u);
}
for(int i=1;i<=n;i++) cin>>a0[i];
tr.init();
for(int i=1;i<=n;i++) m1[i].se=m2[i].se=i;
dfs1(1,-1);
for(int i=1;i<=n;i++) in[i]=m1[i];
dfs2(1,-1);
// for(int i=1;i<=n;i++) cout<<dfn[i]<<" "<<dfnR[i]<<"\n";
// system("pause");
for(int i=1;i<=n;i++)
{
a0[i]%=2;
if(a0[i])
{
if(m1[i].se==par[i])
{
// cout<<"in: "<<i<<" "<<m1[i].fi<<"\n";
// cout<<"out: "<<i<<" "<<m2[i].fi<<"\n";
bt.update(dfn[i],dfnR[i],m1[i].fi);
if(dfn[i]>1) bt.update(1,dfn[i]-1,m2[i].fi);
if(dfnR[i]<n) bt.update(dfnR[i]+1,n,m2[i].fi);
}
else
{
int u=son[i];
// cout<<"in: "<<u<<" "<<m2[i].fi<<"\n";
// cout<<"out: "<<u<<" "<<m1[i].fi<<"\n";
bt.update(dfn[u],dfnR[u],m2[i].fi);
if(dfn[u]>1) bt.update(1,dfn[u]-1,m1[i].fi);
if(dfnR[u]<n) bt.update(dfnR[u]+1,n,m1[i].fi);
}
}
}
// for(int i=1;i<=n;i++) cout<<m1[i].fi<<" "<<m1[i].se<<" "<<m2[i].fi<<" "<<m2[i].se<<"\n";
// for(int i=1;i<=n;i++) cout<<bt.query(dfn[i])<<" "<<m1[i].fi<<"\n";
// system("pause");
for(int i=1;i<=n;i++)
{
a0[i]=bt.query(dfn[i])^bt.query(dfn[par[i]]);
if(par[i]) tr.update(tr.rt[par[i]],a0[i],m1[i].fi,1);
}
while(Q--)
{
int x,y;
cin>>x>>y;
if(m1[x].se==par[x])
{
tr.update(tr.rt[par[x]],a0[x],m1[x].fi,-1);
bt.update(dfn[x],dfnR[x],m1[x].fi);
if(dfn[x]>1) bt.update(1,dfn[x]-1,m2[x].fi);
if(dfnR[x]<n) bt.update(dfnR[x]+1,n,m2[x].fi);
a0[x]=bt.query(dfn[x])^bt.query(dfn[par[x]]);
tr.update(tr.rt[par[x]],a0[x],m1[x].fi,1);
}
else
{
int u=son[x];
tr.update(tr.rt[x],a0[u],m1[u].fi,-1);
bt.update(dfn[u],dfnR[u],m2[x].fi);
if(dfn[u]>1) bt.update(1,dfn[u]-1,m1[x].fi);
if(dfnR[u]<n) bt.update(dfnR[u]+1,n,m1[x].fi);
a0[u]=bt.query(dfn[u])^bt.query(dfn[x]);
tr.update(tr.rt[x],a0[u],m1[u].fi,1);
}
int res=tr.query(tr.rt[y],bt.query(dfn[y]));
if(bt.query(dfn[y])>m1[y].fi) res++;
if(y!=1&&bt.query(dfn[par[y]])>m1[par[y]].fi) res++;
// res=0;
// for(int i=1;i<=n;i++) if((i==y||par[i]==y||par[y]==i)&&bt.query(dfn[i])>m1[i].fi) res++;
// for(int i=1;i<=n;i++) cout<<bt.query(dfn[i])<<" "<<m1[i].fi<<"\n";
cout<<res<<"\n";
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
int dirt;
cin>>dirt;
// cin>>_;
while(_--) solve();
return 0;
}
【USACO 2022 Open】Up Down Subsequence
一个很直接的贪心想法,只需要尽可能最大化长度即可。
令 \(dp(i)\) 表示前 \(i\) 个数能选出的最大长度,转移用两个 BIT 维护。
也不知道这个状态为什么对,感性理解就是牺牲一个长度不会更优,越往后面增加长度越困难。
复杂度 \(O(n \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;
int n;
int a[300005];
char b[300005];
struct BIT
{
int t[300005];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int d)
{
for(int i=x;i<=n;i+=lowbit(i)) t[i]=max(t[i],d);
}
int query(int x)
{
int res=0;
for(int i=x;i>=1;i-=lowbit(i)) res=max(res,t[i]);
return res;
}
}bt1,bt2;
void solve()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++) cin>>b[i];
int ans=1;
for(int i=1;i<=n;i++)
{
int tmp=max(bt1.query(a[i]),bt2.query(n-a[i]+1))+1;
ans=max(ans,tmp);
if(b[tmp]=='U') bt1.update(a[i],tmp);
else bt2.update(n-a[i]+1,tmp);
}
cout<<ans-1<<"\n";
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【ICPC 2022 Jinan】Skills
不能掉到 \(0\) 以下,这个限制特别烦。如果某一天掉到 \(0\) 以下,那么这一天之前所做的练习都是无效的。也就是说一定存在一个最优方案,在开始练习一个技能之后,一定不会掉到 \(0\) 以下。
令 \(dp(i,0/1/2,d_1,d_2)\) 表示,考虑前 \(i\) 天,第 \(i\) 天练习了什么技能,另外两个技能分别有多少天没有练习。一个非常直观的感觉,\(d_1,d_2\) 不会太大,是 \(O(\sqrt {A})\) 级别的,\(A\) 是值域。感性理解就是,超出这个级别会导致掉下来太多,而在这一段没有练习的时间的正中间强制练习这个技能,会取得 \(\ge A\) 的收益,复杂度 \(O(nA)\),注意一下要处理一些技能没开始练习的情况。
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 dp[2][205][205][3];
const int LIM=200;
int n,a[1005][3];
void trans(int &x,int y)
{
if(y>x) x=y;
}
void solve()
{
memset(dp,-0x3f,sizeof(dp));
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i][0]>>a[i][1]>>a[i][2];
for(int i=0;i<3;i++) dp[0][0][0][i]=a[1][i];
int nw=0;
for(int i=2;i<=n;i++)
{
nw^=1;
memset(dp[nw],-0x3f,sizeof(dp[nw]));
for(int j=0;j<=LIM;j++) for(int k=0;k<=LIM;k++) for(int lst=0;lst<3;lst++) if(dp[nw^1][j][k][lst]>=0)
{
array<int,3> cost;
if(lst==0) cost={1,j,k};
if(lst==1) cost={j,1,k};
if(lst==2) cost={j,k,1};
trans(dp[nw][(cost[0]==0?0:cost[0]+1)][(cost[1]==0?0:cost[1]+1)][2],dp[nw^1][j][k][lst]+a[i][2]-cost[0]-cost[1]);
trans(dp[nw][(cost[0]==0?0:cost[0]+1)][(cost[2]==0?0:cost[2]+1)][1],dp[nw^1][j][k][lst]+a[i][1]-cost[0]-cost[2]);
trans(dp[nw][(cost[1]==0?0:cost[1]+1)][(cost[2]==0?0:cost[2]+1)][0],dp[nw^1][j][k][lst]+a[i][0]-cost[1]-cost[2]);
}
}
int ans=0;
for(int i=0;i<=LIM;i++) for(int j=0;j<=LIM;j++) for(int k=0;k<3;k++) ans=max(ans,dp[nw][i][j][k]);
cout<<ans<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
cin>>_;
while(_--) solve();
return 0;
}
【Ptz2021 Winter】Designing a PCB
考虑 2-sat,有 \(2n\) 个 \(0/1\) 变量 \(x_0,x_1,\cdots,x_{2n-1}\),每个变量表示 \(x\) 轴上的一个点所在连线的方向,是向上还是向下的。想到这个,剩下的通过手玩都可以解决。
令 \(l_i,r_i\) 表示颜色 \(i\) 靠左,靠右的两个点的位置。
- 若 \(l_i \lt l_j \lt r_j \lt r_i\),即区间包含,可以得到 \(x_{l_j}=x_{r_j}\)。
- 若 \(l_i \lt l_j \lt r_i \lt r_j\),即区间相交,可以得到 \(x_{l_j} \neq x_{r_i}\)。
手玩 \(2^4\) 种情况会发现是必要且充分的,构造方案可以对每个 \(l_i\) 上的竖线钦定长度,\(l_i\) 越小越长。用 2-sat 或并查集求解,复杂度 \(O(n^2)\)。可以主席树优化建图,复杂度 \(O(n \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;
int n,L[1005],R[1005];
int U[2005],D[2005],uidx;
vector <int> g[4005];
int dfn[1000005],low[1000005],times,scccnt,bl[1000005],vis[1000005],stk[1000005],top;
void tarjan(int u)
{
dfn[u]=low[u]=++times;
vis[u]=1,stk[++top]=u;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
scccnt++;
while(1)
{
int x=stk[top];
top--;
bl[x]=scccnt,vis[x]=0;
if(x==u) break;
}
}
}
void solve()
{
cin>>n;
for(int i=1;i<=2*n;i++)
{
int x;
cin>>x;
if(!L[x]) L[x]=i;
else R[x]=i;
}
for(int i=1;i<=2*n;i++) U[i]=++uidx,D[i]=++uidx;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i!=j&&L[i]<L[j]&&L[j]<R[i]&&R[i]<R[j])
{
int x=L[j],y=R[i];
// cout<<x<<" "<<y<<" "<<"different\n";
g[U[x]].pb(D[y]),g[D[x]].pb(U[y]);
g[U[y]].pb(D[x]),g[D[y]].pb(U[x]);
}
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i!=j&&L[i]<L[j]&&L[j]<R[j]&&R[j]<R[i])
{
int x=L[j],y=R[j];
// cout<<x<<" "<<y<<" "<<"same\n";
g[U[x]].pb(U[y]),g[D[x]].pb(D[y]);
g[U[y]].pb(U[x]),g[D[y]].pb(D[x]);
}
for(int i=1;i<=uidx;i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=2*n;i++) if(bl[U[i]]==bl[D[i]])
{
cout<<"NO\n";
return;
}
cout<<"YES\n";
for(int i=1;i<=n;i++)
{
int len=2*n-L[i];
char dl=(bl[U[L[i]]]<bl[D[L[i]]]?'U':'D');
char dr=(bl[U[R[i]]]<bl[D[R[i]]]?'U':'D');
if(dl==dr) cout<<3<<" "<<dl<<" "<<len<<" "<<'R'<<" "<<R[i]-L[i]<<" "<<(dr=='U'?'D':'U')<<" "<<len<<"\n";
else if(dl=='U') cout<<5<<" "<<'U'<<" "<<len<<" "<<'R'<<" "<<4*n-L[i]-L[i]+1<<" "<<'D'<<" "<<2*len<<" "<<'L'<<" "<<4*n-L[i]-R[i]+1<<" "<<'U'<<" "<<len<<"\n";
else cout<<5<<" "<<'D'<<" "<<len<<" "<<'R'<<" "<<4*n-L[i]-L[i]+1<<" "<<'U'<<" "<<2*len<<" "<<'L'<<" "<<4*n-L[i]-R[i]+1<<" "<<'D'<<" "<<len<<"\n";
}
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【UNR #4】序列妙妙值
做前缀和,代价可以写成 \(\sum_{i=1}^{n} s_{p_i-1} \space \text{xor} \space s_{p_i}\)。
令 \(dp(k,i)\) 表示前缀 \([1,i]\) 分 \(k\) 段的答案,转移考虑一种类似于 meet-in-middle 的思想,从小到大枚举 \(k\),在每一轮中,从小到大枚举 \(i\),固定 \(s_{i-1}\) 的前 \(8\) 位,枚举所有后 \(8\) 位的情况,开桶记录。然后固定 \(s_i\) 的后 \(8\) 位,枚举前 \(8\) 位的所有情况,到桶里查询并更新 dp 值,复杂度 \(O(kn\sqrt{A})\),\(A\) 是值域,
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,k;
int a[60005];
int dp[9][60005];
int g[1005][1005];
void solve()
{
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i],a[i]^=a[i-1];
memset(dp,0x3f,sizeof(dp)),memset(g,0x3f,sizeof(g));
dp[0][0]=0;
for(int i=1;i<=k;i++)
{
memset(g,0x3f,sizeof(g));
for(int j=0;j<=n;j++)
{
for(int l=0;l<(1<<8);l++) dp[i][j]=min(dp[i][j],g[(a[j]>>8)^l][(a[j]&255)]+(l<<8));
for(int l=0;l<(1<<8);l++) g[a[j]>>8][(a[j]&255)^l]=min(g[a[j]>>8][(a[j]&255)^l],dp[i-1][j]+l);
}
}
for(int i=k;i<=n;i++) cout<<dp[k][i]<<" ";
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【UNR #4】网络恢复
将所有边随机分成大小相等的 \(50\) 份,对每一份进行一次询问,\(a_i\) 随机生成,得到的 \(b_i\) 可以看做是对 \(i\) 的邻居集合进行 XOR-Hashing 的结果。
如果是一棵树的话,可以逐步删叶子。这个想法可以拓展到图上,先逐步删度数为 \(1\) 的点,如果没有了,就随便取一个点,有很大概率其度数为 \(2\),枚举一个点,用哈希表等数据结构查询另一个点,然后继续删度数为 \(1\) 的点。
复杂度玄学,正确性玄学,反正过了。
Code
#include "explore.h"
#include<bits/stdc++.h>
#define ull unsigned long long
#define pb push_back
using namespace std;
mt19937_64 rnd(114514);
int n,m;
unordered_map <ull,int> ma;
unordered_map <int,bool> g[50005];
void report(int u,int v)
{
if(g[u].find(v)!=g[u].end()||g[v].find(u)!=g[v].end()) return;
Report(u,v);
g[u][v]=g[v][u]=1;
}
int get(vector <ull> &A,vector <ull> &B,vector <int> vis)
{
for(int i=1;i<=n;i++) if(!vis[i-1]&&B[i-1])
{
for(int j=1;j<=n;j++) if(!vis[j-1]&&B[j-1]&&i!=j&&ma.find(B[i-1]^A[j-1])!=ma.end())
{
// cout<<"find: "<<i<<" "<<j<<"\n";
report(i,j);
B[i-1]^=A[j-1],B[j-1]^=A[i-1];
return i;
}
}
return -1;
}
void work(vector <int> S)
{
if(!S.size()) return;
vector <ull > A;
vector <int> vis;
for(int i=0;i<n;i++) vis.pb(0);
ma.clear();
for(int i=0;i<n;i++) A.pb(rnd()),ma[A[i]]=i+1;
vector <ull> B=Query(A,S);
while(1)
{
queue <int> q;
for(int i=1;i<=n;i++) if(ma.find(B[i-1])!=ma.end()) q.push(i);
while(q.size())
{
int u=q.front();
q.pop();
vis[u-1]=1;
// cout<<u<<" visited\n";
int v=ma[B[u-1]];
if(!v) continue;
report(u,v);
B[v-1]^=A[u-1],B[u-1]^=A[v-1];
if(ma.find(B[v-1])!=ma.end()) q.push(v);
}
int tmp=get(A,B,vis);
if(tmp==-1) return;
q.push(tmp);
}
}
void Solve(int _n,int _m)
{
n=_n,m=_m;
vector <int> es;
for(int i=1;i<=m;i++) es.pb(i);
for(int i=m-1;i>=0;i--) swap(es[i],es[rnd()%(i+1)]);
vector <int> que[51];
for(int i=0;i<m;i++) que[i%50].pb(es[i]);
for(int i=0;i<50;i++) work(que[i]);
}
【UNR #4】己酸集合
\((x_i,y_i)\) 到 \((0,z_j)\) 的距离是 \(\sqrt{x_i^2+y_i^2+z_j^2+2y_iz_j}\),对于一个询问,求上式 \(\le R\) 的 \(i\) 就行,条件还可以写成:\(2y_iz_j+x_i^2+y_i^2 \le R^2-z_j^2\),将左边看成一条斜率为 \(2y_i\),截距为 \(x_i^2+y_i^2\) 的直线,查询就是求 \(x=z_j\) 处 \(y \le R^2-z_j^2\) 的直线个数。
将询问离线,从小到大枚举 \(x\),维护直线按 \(y\) 排序的结果,由于两条直线只有一个交点,故只会发生 \(O(n^2)\) 次相邻交换操作,维护复杂度 \(O(n^2 \log)\),查询复杂度 \(O(Q \log)\)。
考虑分块,将 \(B\) 个直线一起处理,做 \(n/B\) 次,复杂度 \(O(nB^2\log/B+nQ\log/B)\),令 \(B=\sqrt{Q}\),复杂度 \(O(n \sqrt Q \log)\)。
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=2e18;
const int B=1000;
char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
return x*f;
}
void print(long long x) {
if(x>9) print(x/10);
*O++=x%10+'0';
}
int ans[1000005];
int X[1005],Y[1005],n;
ll k[1005],b[1005];
void add(int x,int y)
{
n++;
X[n]=x,Y[n]=y,k[n]=-2LL*y,b[n]=1LL*x*x+1LL*y*y;
}
ll getX(int i,int j)
{
if(k[i]==k[j]) return INF;
ll p=b[j]-b[i],q=k[i]-k[j];
if(abs(p)%abs(q)==0) return p/q+1;
if(p<0) p*=-1,q*=-1;
if(p>0&&q>0) return (p+q-1)/q;
return p/q;
}
ll pos[1000005];
int U[1000005],V[1000005];
int a[1005],P[1005];
bool cmp(int x,int y)
{
return pos[x]<pos[y];
}
ll calc(int i,int x)
{
return 1LL*k[i]*x+b[i];
}
int nowx;
bool cmp2(int x,int y)
{
return calc(x,nowx)<calc(y,nowx);
}
vector <int> vec;
void init()
{
int idx=0;
for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) idx++,vec.pb(idx),pos[idx]=getX(i,j),U[idx]=i,V[idx]=j;
sort(vec.begin(),vec.end(),cmp);
// for(int j=0;j<vec.size();j++) cout<<pos[vec[j]]<<" "<<U[vec[j]]<<" "<<V[vec[j]]<<"\n";
}
void query(vector <array<ll,3> > que) // x,y,id
{
nowx=-1000000000;
for(int i=1;i<=n;i++) a[i]=i;
sort(a+1,a+1+n,cmp2);
// for(int l=1;l<=n;l++) cout<<a[l]<<" "<<k[a[l]]<<" "<<b[a[l]]<<"\n";
// system("pause");
for(int i=1;i<=n;i++) P[a[i]]=i;
int j=0;
while(j<vec.size()&&pos[vec[j]]<nowx) j++;
for(int i=0;i<que.size();i++)
{
ll px=que[i][0],py=que[i][1];
nowx=px;
vector <int> re;
re.clear();
while(j<vec.size()&&pos[vec[j]]<=nowx) re.pb(U[vec[j]]),re.pb(V[vec[j]]),j++;
sort(re.begin(),re.end()),re.resize(unique(re.begin(),re.end())-re.begin());
sort(re.begin(),re.end(),cmp2);
vector <int> po;
po.clear();
for(int l=0;l<re.size();l++) po.pb(P[re[l]]);
sort(po.begin(),po.end());
// for(int l=0;l<re.size();l++) cout<<re[l]<<" ";
// cout<<"\n";
// for(int l=0;l<po.size();l++) cout<<po[l]<<" ";
// cout<<"\n";
for(int l=0;l<re.size();l++) a[po[l]]=re[l],P[re[l]]=po[l];
// cout<<que[i][2]<<" "<<que[i][0]<<" "<<que[i][1]<<" ...:\n";
// for(int l=1;l<=n;l++) cout<<a[l]<<" "<<k[a[l]]<<" "<<b[a[l]]<<"\n";
// system("pause");
int L=1,R=n,res=0;
while(L<=R)
{
int mid=(L+R)>>1;
if(calc(a[mid],px)<=py) res=mid,L=mid+1;
else R=mid-1;
}
ans[que[i][2]]+=res;
}
}
int XX[12005],YY[12005];
void solve()
{
int NN=read(),QQ=read();
for(int i=1;i<=NN;i++) XX[i]=read(),YY[i]=read();
vector <array<ll,3> > que;
que.clear();
for(int i=1;i<=QQ;i++)
{
int z=read(),R=read();
que.pb({z,1LL*R*R-1LL*z*z,i});
}
sort(que.begin(),que.end());
for(int i=1;i<=NN;i++)
{
add(XX[i],YY[i]);
if(n==B||i==NN)
{
init(),query(que);
memset(X,0,sizeof(X));
memset(Y,0,sizeof(Y));
memset(k,0,sizeof(k));
memset(b,0,sizeof(b));
memset(pos,0,sizeof(pos));
memset(U,0,sizeof(U));
memset(V,0,sizeof(V));
memset(a,0,sizeof(a));
memset(P,0,sizeof(P));
vec.clear();
n=0;
}
}
for(int i=1;i<=QQ;i++) print(ans[i]),*O++=10;
fwrite(obuf,O-obuf,1,stdout);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【UNR #5】提问系统
可以把操作序列写成一棵树,限制可以看成对于每个点 \(u\),根到 \(u\) 的路径上 R
的个数在区间 \([L_u,R_u]\) 中。
先拆一下组合意义,\(p_r,p_b^2\) 可以看做,选择一个 R
和两个 B
构成有序三元组 \((r,b_1,b_2)\) 的方案数。
令 \(dp(u,L,R,0/1,0/1,0/1)\) 表示,考虑填 \(u\) 的子树,未来 \(1\) 到 \(u\) 的路径上可以填 \([L,R]\) 个 R
,三元组的三个位置是否都被填的方案数。而我们可以将这个状态加到区间 \([L,R]\) 上,故我们可以更改状态,\(dp(u,cnt,0/1,0/1,0/1)\) 表示,\(1\) 到 \(u\) 的路径上恰好填 \(cnt\) 个 R
,复杂度 \(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 par[2505],dep[2505];
vector <int> g[2505];
int dp[2505][2505][8],f[2][8],tmp[2505][8];
int limR,limB,n;
void dfs(int u)
{
if(!g[u].size())
{
for(int i=max(0LL,dep[u]-limB);i<=limR;i++)
{
if(i) dp[u][i-1][0]++,dp[u][i-1][1]++;
dp[u][i][0]++,dp[u][i][2]++,dp[u][i][4]++,dp[u][i][6]++;
}
return;
}
for(int i=0;i<g[u].size();i++) dfs(g[u][i]);
int L=max(0LL,dep[u]-limB),R=limR;
for(int cr=L;cr<=R;cr++)
{
memset(f,0,sizeof(f));
int nw=0;
for(int i=0;i<8;i++) f[nw][i]=dp[g[u][0]][cr][i];
for(int i=1;i<g[u].size();i++)
{
nw^=1;
memset(f[nw],0,sizeof(f[nw]));
for(int j=0;j<8;j++) for(int l=7-j;;l=(l-1)&(7-j))
{
f[nw][j|l]=(f[nw][j|l]+f[nw^1][j]*dp[g[u][i]][cr][l])%mod;
if(!l) break;
}
}
for(int i=0;i<8;i++) dp[u][cr][i]=f[nw][i];
}
if(!u) return;
memset(tmp,0,sizeof(tmp));
for(int cr=L;cr<=R;cr++) for(int i=0;i<8;i++)
{
if(cr)
{
if(i%2==0) tmp[cr-1][i+1]=(tmp[cr-1][i+1]+dp[u][cr][i])%mod;
tmp[cr-1][i]=(tmp[cr-1][i]+dp[u][cr][i])%mod;
}
for(int l=0;l<8;l+=2) if((l&i)==0)
tmp[cr][i+l]=(tmp[cr][i+l]+dp[u][cr][i])%mod;
}
memset(dp[u],0,sizeof(dp[u]));
for(int cr=L-1;cr<=R;cr++) for(int i=0;i<8;i++)
dp[u][cr][i]=tmp[cr][i];
}
void solve()
{
cin>>n>>limR>>limB;
int u=0;
for(int i=1,j=0;i<=2*n;i++)
{
string s;
cin>>s;
if(s=="push") j++,par[j]=u,dep[j]=dep[u]+1,g[u].pb(j),u=j;
else u=par[u];
}
dfs(0);
cout<<dp[0][0][7];
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【UNR #6】神隐
考虑二进制分组,对于每一位,把所有这一位为 \(0\) 和为 \(1\) 的编号拿出来做一次询问,总共问 \(2\lceil \log n \rceil\) 次,一条边 \((u,v)\) 存在当且仅当恰好在一半的询问中 \(u,v\) 连通。证明:
- 若 \((u,v)\) 有边,正确性显然。
- 若 \((u,v)\) 没有边,则 \((u,v)\) 路径上有 \(\ge 2\) 条边,任意取两条边 \(x,y\),由于所有边编号不同,故一定存在一次包含 \(x\) 的询问不包含 \(y\),即同时询问 \(x,y\) 的次数严格小于一半,即 \(u,v\) 连通的询问次数严格小于一半。
再考虑进行一个映射:将所有边重标号为两两不同的,\(0,1\) 出现次数相等的二进制数,故只需要对所有二进制位,把这一位为 \(1\) 的所有边拿出来询问。
寻找答案可以不断删叶子,叶子的判定条件是在恰好一半的询问中是孤点,父亲就是另一半询问中这个叶子所在连通块的交,这个交只有一个点,因为在这些询问中,连向父亲的其它边都至少被断了一次。
这个还是不好做,我们额外维护删去所有叶子的连通情况,一定可以找到一个叶子,比如最深的叶子,其对应连通块只有一个点,就是父亲。
时间复杂度 \(O(n \log n)\)。
Code
#include "tree.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;
vector <int> S1[21][140005],S2[21][140005];
int sz1[21][140005],sz2[21][140005];
bool del1[140005],del2[140005];
int lg;
vector <pii > bl[140005];
int chk(int x)
{
int cnt=0;
for(int i=0;i<bl[x].size();i++) if(sz1[bl[x][i].fi][bl[x][i].se]==1) cnt++;
if(cnt!=lg/2) return 0;
return 1;
}
int get_fa(int x)
{
for(int i=0;i<bl[x].size();i++) if(sz2[bl[x][i].fi][bl[x][i].se]==1)
{
int u=bl[x][i].fi,v=bl[x][i].se;
while(del2[S2[u][v].back()]) S2[u][v].pop_back();
return S2[u][v].back();
}
return -1;
}
vector <pii > solve(int n)
{
lg=(n<=2000?14:20);
vector <int> ma;
for(int i=0;i<(1<<lg);i++) if(__builtin_popcount(i)==lg/2&&ma.size()<n-1) ma.pb(i);
for(int i=0;i<lg;i++)
{
vector <int> vec;
vec.clear();
for(int j=0;j<n-1;j++)
{
if(ma[j]&(1<<i)) vec.pb(1);
else vec.pb(0);
}
vector <vector<int> > tmp=query(vec);
for(int j=0;j<tmp.size();j++) for(int l=0;l<tmp[j].size();l++)
{
S1[i+1][j+1].pb(tmp[j][l]+1),S2[i+1][j+1].pb(tmp[j][l]+1);
sz1[i+1][j+1]++,sz2[i+1][j+1]++;
bl[tmp[j][l]+1].pb(mp(i+1,j+1));
}
}
vector <pii > ans;
if(n==1) return ans;
for(int i=1;i<=n;i++) if(chk(i))
{
del2[i]=1;
for(int j=0;j<bl[i].size();j++) sz2[bl[i][j].fi][bl[i][j].se]--;
}
queue <pii > q;
for(int i=1;i<=n;i++) if(chk(i))
{
int fa=get_fa(i);
if(fa!=-1) q.push(mp(i,fa));
}
while(ans.size()<n-2)
{
// cerr<<ans.size()<<" "<<q.size()<<"\n";
int u=q.front().fi,fa=q.front().se;
q.pop();
if(del1[u]) continue;
ans.pb(mp(u,fa));
del1[u]=1;
for(int i=0;i<bl[u].size();i++) sz1[bl[u][i].fi][bl[u][i].se]--;
if(chk(fa))
{
u=fa,del2[u]=1;
for(int i=0;i<bl[u].size();i++)
{
int x=bl[u][i].fi,y=bl[u][i].se;
sz2[x][y]--;
if(sz2[x][y]==1)
{
while(del2[S2[x][y].back()]) S2[x][y].pop_back();
for(int j=0;j<S1[x][y].size();j++)
{
int v=S1[x][y][j];
if(!del1[v]&&v!=S2[x][y].back()) q.push(mp(v,S2[x][y].back()));
}
}
}
}
}
pii t=mp(-1,-1);
for(int i=1;i<=n;i++) if(!del1[i])
{
if(t.fi==-1) t.fi=i;
else t.se=i;
}
ans.pb(t);
for(int i=0;i<ans.size();i++) ans[i].fi--,ans[i].se--;
return ans;
}
【UNR #3】To Do Tree
神秘贪心,一个最直接想法是按儿子个数贪心,这个显然不对,比如一个菊花,上面再加一个节点,就可以欺骗这个算法不去解锁这个菊花。
但是按子树大小贪心就是对的!也不知道为什么,用 pq 维护,复杂度 \(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
const int mod=998244353;
const int inf=0x3f3f3f3f;
vector <int> g[100005],ansv[100005];
int n,m,ans,sz[100005],fa[100005];
priority_queue <pii > pq;
void solve()
{
cin>>n>>m;
for(int i=2;i<=n;i++) cin>>fa[i],g[fa[i]].pb(i);
for(int i=n;i>=1;i--) sz[i]++,sz[fa[i]]+=sz[i];
pq.push(mp(sz[1],1));
while(pq.size())
{
ans++;
vector <int> vec;
for(int i=1;i<=m&&pq.size();i++) vec.pb(pq.top().se),pq.pop();
ansv[ans]=vec;
for(int i=0;i<vec.size();i++)
{
int u=vec[i];;
for(int j=0;j<g[u].size();j++) pq.push(mp(sz[g[u][j]],g[u][j]));
}
}
cout<<ans<<"\n";
for(int i=1;i<=ans;i++)
{
cout<<ansv[i].size()<<" ";
for(int j=0;j<ansv[i].size();j++) cout<<ansv[i][j]<<" ";
cout<<"\n";
}
}
signed main()
{
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【Ptz2021 Summer】Minimal Cyclic Shift
先考虑循环节,等概率选取一个长度为 \(n\) 循环节为 \(d\) 的字符串,考察最小表示在 \(f\) 处的概率,若 \(f \le d\),则概率为 \(d^{-1}\),否则为 \(0\)。
先计算循环节为 \(d\) 的字符串个数 \(cnt(d)\),这个可以用莫反求,\(cnt(d)=\sum_{c|d} \mu(c)26^{d/c}\)。
然后问题就变成了,每次询问两个数 \(m,n\),求等概率选择一个长度为 \(n\) 的字符串和一个长度为 \(m\) 的字符串,最小表示位置相同的概率。枚举位置 \(f\),概率是 \((\sum_{d|n, f \le d}cnt(d)d^{-1}26^{-n}) \cdot (\sum_{d|m, f \le d}cnt(d)d^{-1}26^{-m})\)。
观察到两部分都可以被分为至多 \(\sqrt{A}\) 段相等的连续段,\(A\) 是值域,复杂度 \(O(n \sqrt{A})\)。
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;
}
const int N=100000;
int n,a[100005],pw[100005],inv[100005],invn[100005];
int miu[100005],g[100005],flg[100005],p[100005],tot;
void sieve()
{
miu[1]=1;
for(int i=2;i<=N;i++)
{
if(!flg[i]) p[++tot]=i,miu[i]=-1;
for(int j=1;j<=tot&&i*p[j]<=N;j++)
{
flg[i*p[j]]=1;
if(i%p[j]==0)
{
miu[i*p[j]]=0;
break;
}
miu[i*p[j]]=-miu[i];
}
}
}
int calc(int l1,int l2)
{
vector <pii > d1,d2;
vector <int> rg;
rg.pb(min(l1,l2));
for(int i=1;i*i<=l1;i++) if(l1%i==0)
{
d1.pb(mp(i,invn[i]*g[i]%mod*inv[l1]%mod));
if(l1/i!=i) d1.pb(mp(l1/i,invn[l1/i]*g[l1/i]%mod*inv[l1]%mod));
rg.pb(i),rg.pb(l1/i);
}
for(int i=1;i*i<=l2;i++) if(l2%i==0)
{
d2.pb(mp(i,invn[i]*g[i]%mod*inv[l2]%mod));
if(l2/i!=i) d2.pb(mp(l2/i,invn[l2/i]*g[l2/i]%mod*inv[l2]%mod));
rg.pb(i),rg.pb(l2/i);
}
rg.pb(0);
sort(d1.begin(),d1.end()),sort(d2.begin(),d2.end());
sort(rg.begin(),rg.end()),rg.resize(unique(rg.begin(),rg.end())-rg.begin());
int res=0;
int sum1=0,sum2=0;
for(int i=rg.size()-1,x1=d1.size()-1,x2=d2.size()-1,s1=0,s2=0;i>=1;i--)
{
int L=rg[i-1]+1,R=rg[i];
// cout<<L<<" "<<R<<"\n";
while(x1>=0&&d1[x1].fi>=L) s1=(s1+d1[x1].se)%mod,x1--;
while(x2>=0&&d2[x2].fi>=L) s2=(s2+d2[x2].se)%mod,x2--;
res=(res+(R-L+1)*s1%mod*s2%mod)%mod;
sum1=(sum1+(R-L+1)*s1)%mod;
sum2=(sum2+(R-L+1)*s2)%mod;
}
// cout<<res<<" "<<sum1<<" "<<sum2<<"\n";
return res;
}
void solve()
{
// cout<<351LL*fpow(676,mod-2)%mod<<"\n";
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
pw[0]=1;
for(int i=1;i<=N;i++) pw[i]=26LL*pw[i-1]%mod;
for(int i=0;i<=N;i++) inv[i]=fpow(pw[i],mod-2),invn[i]=fpow(i,mod-2);
sieve();
for(int i=1;i<=N;i++) for(int d=1;d*d<=i;d++) if(i%d==0)
{
g[i]=(g[i]+miu[d]*pw[i/d]%mod+mod)%mod;
if(i/d!=d) g[i]=(g[i]+miu[i/d]*pw[d]%mod+mod)%mod;
}
// for(int i=1;i<=10;i++) cout<<miu[i]<<" "<<g[i]<<"\n";
int ans=0;
if(n==1)
{
cout<<1<<"\n";
return;
}
for(int i=1;i<=n;i++) ans=(ans+calc(a[i],a[(i==1?n:i-1)]));
cout<<ans%mod<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【UNR #7】那些你不要的
一次操作会删去原序列中一个奇数位置的数和偶数位置的数,原序列偶数位置的数不可能留下来,所以最优策略是选择奇数位置最大/最小的数删去,答案就是奇数位置的中位数。
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;
const int INF=1e18;
int n,a[1000005],b[1000005];
void solve()
{
cin>>n;
int m=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(i%2==1) b[++m]=a[i];
}
sort(b+1,b+1+m);
if(m%2==1) cout<<b[(m+1)/2]<<"\n";
else cout<<b[m/2+1]<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
cin>>_;
while(_--) solve();
return 0;
}
【UNR #7】比特迷宫
一次操作是,选择一个区间,将其异或上模 \(2\) 意义下杨辉三角的第区间长度行。
赛时的想法是,考虑一个阈值 \(B\),对于所有长度 \(\le B\) 的二进制串暴力求得最优解,然后将给出的序列划分成若干长度 \(\le B\) 的段,每一段用最优的方式操作,令 \(B=20\) 可以在大约 \(168000\) 次通过前三个子任务,稍微把 \(B\) 调大,操作次数大概在 \(164000\) 左右,特别难受。
还有一个想法是,同样预处理 \(\le B\) 的所有情况,然后 \(dp(i,mask)\) 表示考虑给定序列前 \(i\) 位,最后几位是 \(mask\) 的最优步数,没写,不知道表现如何。
赛后写的做法是,从左往右扫,碰到一个 \(1\) 的时候,考虑以它为区间左端点进行一次操作,枚举所有长度 \(\le B\) 的情况,取剩余 \(1\) 最少的操作,令 \(B=550\),随机数据下表现很优秀,大约 \(157000\) 次。
如果数据不随机的话,可以在操作开始前随机进行若干次操作把序列打乱,实测操作 \(1000\) 次会变得比较随机。
Code
#pragma GCC optimize("Ofast")
#pragma GCC target("avx")
#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;
const int INF=1e18;
const int LEN=550;
bitset <LEN+5> C[LEN+5];
vector <pii > ans;
int n,K,T,a[1100005],tmpa[1100005];
mt19937 rnd(time(0));
void solve()
{
cin>>n>>K>>T;
for(int i=0;i<n;i++) cin>>a[i],tmpa[i]=a[i];
while(1)
{
ans.clear();
for(int i=0;i<n;i++) a[i]=tmpa[i];
for(int _=0;_<1000;_++)
{
int i=rnd()%n,j=rnd()%(n-i);
for(int l=j;;l=(l-1)&j)
{
a[i+l]^=1;
if(!l) break;
}
ans.pb(mp(i,j));
}
// cerr<<"...\n";
for(int i=0;i<n;i++) if(a[i])
{
bitset <LEN+5> nw;
nw.reset();
for(int j=i;j<min(n,i+LEN);j++) if(a[j]) nw[j-i]=1;
int minn=inf,b=-1;
for(int j=0;j<min(LEN,n-i);j++)
{
int cst=(nw^C[j]).count();
if(cst<minn) minn=cst,b=j;
}
ans.pb(mp(i,b));
for(int j=0;j<LEN;j++) if(C[b][j]) a[i+j]^=1;
}
cerr<<ans.size()<<"\n";
if(ans.size()>T) continue;
cout<<ans.size()<<"\n";
for(int i=0;i<ans.size();i++) cout<<ans[i].fi<<" "<<ans[i].se<<"\n";
return;
}
}
signed main()
{
C[0][0]=1;
for(int i=1;i<LEN;i++)
{
C[i][0]=C[i][i]=1;
for(int j=1;j<i;j++) C[i][j]=C[i-1][j-1]^C[i-1][j];
}
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【UNR #7】火星式选拔
先考虑 \(k=1\),目前的数 \((a_i,b_i)\) 会在之后第一个满足 \(a_j \ge b_i\) 时候被淘汰,每次询问可以倍增求解。
对于一般情况,观察到区间内的前 \(k-1\) 大的 \(b_i\) 一定会留下,找出区间内前 \(k\) 大中下标的最大值 \(R\),从 \(R+1\) 开始,第 \(k\) 大的 \(b_i\) 可能会被淘汰,按照 \(k=1\) 的方法倍增找就行,复杂度 \(O(n \log n)\)。
Code
#pragma GCC optimize("Ofast")
#pragma GCC target("avx")
#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;
char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
return x*f;
}
void print(long long x) {
if(x>9) print(x/10);
*O++=x%10+'0';
}
int Rt2[500005];
struct Segtree2
{
int uidx;
int ls[11000005],rs[11000005],maxx[11000005];
int build(int l,int r)
{
int u=++uidx;
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 u=++uidx;
ls[u]=ls[id],rs[u]=rs[id],maxx[u]=max(maxx[id],x);
if(l==r) return u;
int mid=(l+r)>>1;
if(x<=mid) ls[u]=update(ls[id],l,mid,x);
else rs[u]=update(rs[id],mid+1,r,x);
return u;
}
int querymax(int id,int l,int r,int x,int y)
{
if(x<=l&&r<=y) return maxx[id];
int mid=(l+r)>>1;
int res=0;
if(x<=mid) res=max(res,querymax(ls[id],l,mid,x,y));
if(y>mid) res=max(res,querymax(rs[id],mid+1,r,x,y));
return res;
}
}st2;
struct Segtree
{
int uidx;
int ls[11000005],rs[11000005],cnt[11000005];
ll val[11000005];
int build(int l,int r)
{
int u=++uidx;
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 d)
{
int u=++uidx;
ls[u]=ls[id],rs[u]=rs[id],cnt[u]=cnt[id]+1,val[u]=val[id]+1LL*d;
if(l==r) return u;
int mid=(l+r)>>1;
if(x<=mid) ls[u]=update(ls[id],l,mid,x,d);
else rs[u]=update(rs[id],mid+1,r,x,d);
return u;
}
ll queryval(int id,int l,int r,int x,int y)
{
if(x<=l&&r<=y) return val[id];
int mid=(l+r)>>1;
ll res=0;
if(x<=mid) res+=queryval(ls[id],l,mid,x,y);
if(y>mid) res+=queryval(rs[id],mid+1,r,x,y);
return res;
}
int querykth(int p,int q,int l,int r,int k)
{
// cout<<p<<" "<<q<<" "<<cnt[p]<<" "<<cnt[q]<<" "<<" "<<l<<" "<<r<<" "<<k<<"\n";
if(l==r) return l;
int mid=(l+r)>>1;
if(cnt[rs[p]]-cnt[rs[q]]>=k) return querykth(rs[p],rs[q],mid+1,r,k);
return querykth(ls[p],ls[q],l,mid,k-cnt[rs[p]]+cnt[rs[q]]);
}
}st;
int n,Q;
int a[500005],b[500005],c[500005],Rt[500005],posb[500005];
int fa[20][500005];
struct ST_table
{
int st[20][500005],Lg[500005];
void init()
{
Lg[1]=0,Lg[2]=1;
for(int i=3;i<=n;i++) Lg[i]=Lg[i/2]+1;
for(int i=1;i<=n;i++) st[0][i]=a[i];
for(int k=1;k<20;k++) for(int i=1;i+(1<<k)-1<=n;i++) st[k][i]=max(st[k-1][i],st[k-1][i+(1<<(k-1))]);
}
int query(int l,int r)
{
int s=Lg[r-l+1];
return max(st[s][l],st[s][r-(1<<s)+1]);
}
int get_next(int x,int lim)
{
int res=-1,L=x+1,R=n;
while(L<=R)
{
int mid=(L+R)>>1;
if(query(x+1,mid)>=lim) res=mid,R=mid-1;
else L=mid+1;
}
return res;
}
}tb;
void solve()
{
n=read(),Q=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++) b[i]=read(),posb[b[i]]=i;
for(int i=1;i<=n;i++) c[i]=read();
// Rt[n+1]=st.build(1,n);
// for(int i=n;i>=1;i--) Rt[i]=st.update(Rt[i+1],1,n,posb[i],c[posb[i]]);
Rt[0]=st.build(1,n);
for(int i=1;i<=n;i++) Rt[i]=st.update(Rt[i-1],1,n,b[i],c[i]);
Rt2[n+1]=st2.build(1,n);
for(int i=n;i>=1;i--) Rt2[i]=st2.update(Rt2[i+1],1,n,posb[i]);
memset(fa,-1,sizeof(fa));
tb.init();
for(int i=1;i<=n;i++) fa[0][i]=tb.get_next(i,b[i]);
for(int k=1;k<20;k++) for(int i=1;i<=n;i++) if(fa[k-1][i]!=-1) fa[k][i]=fa[k-1][fa[k-1][i]];
while(Q--)
{
int l=read(),r=read(),k=read();
ll ans=0;
int kth=st.querykth(Rt[r],Rt[l-1],1,n,k);
// cout<<kth<<"\n";
if(kth+1<=n) ans+=st.queryval(Rt[r],1,n,kth+1,n)-st.queryval(Rt[l-1],1,n,kth+1,n);
// cout<<ans<<"\n";
int mx=st2.querymax(Rt2[kth],1,n,l,r);
// cout<<b[mx]<<" "<<mx<<"\n";
kth=posb[kth];
int u=tb.get_next(mx,b[kth]);
// cout<<kth<<" "<<mx<<" "<<" "<<u<<"\n";
if(u==-1||u>r)
{
ans+=1LL*c[kth];
print(ans),*O++=10;
continue;
}
for(int i=19;i>=0;i--) if(fa[i][u]!=-1&&fa[i][u]<=r) u=fa[i][u];
ans+=1LL*c[u];
print(ans),*O++=10;
}
fwrite(obuf,O-obuf,1,stdout);
}
signed main()
{
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
// ios::sync_with_stdio(0);
// cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【UER #11】科考工作
奶一口一定有解,而且解的密度很大。考虑一个随机化做法,每一轮将 \(A_i\) 随机打乱,检查是否有一个区间满足条件,这个是可以 \(O(n)\) 判断一次的。
观察到 \(0\) 比较特殊,将所有 \(0\) 拿出来,令 \(0\) 的个数为 \(cnt\),则只需要找到一个长度 \(\in [N-cnt,N]\) 且和是 \(N\) 的倍数的区间,就能构造出答案。
显然,我们需要让 \(0\) 尽可能多,先找出众数,把每个数减去众数,就可以得到最多的 \(0\)。正确性玄学,感性理解就是,如果只有很少的数出现,则 \(0\) 会很多,如果有很多的数出现,则大致可以看做序列随机,而序列随机只需要尝试 \(O(1)\) 次。
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;
pii a[600005];
vector <pii > v1,v2;
int cnt[600005];
vector <int> sum[600005];
mt19937 rnd(114514);
void solve()
{
cin>>n;
for(int i=1;i<=2*n-1;i++) cin>>a[i].fi,a[i].se=i,cnt[a[i].fi]++;
int maxx=0;
for(int i=1;i<n;i++) if(cnt[i]>cnt[maxx]) maxx=i;
for(int i=1;i<=2*n-1;i++)
{
a[i].fi=(a[i].fi-maxx+n)%n;
if(a[i].fi) v1.pb(a[i]);
else v2.pb(a[i]);
}
if(v2.size()>=n)
{
for(int i=0;i<n;i++) cout<<v2[i].se<<" ";
return;
}
while(1)
{
for(int i=0;i<n;i++) sum[i].clear();
for(int i=v1.size()-1;i>=0;i--) swap(v1[i],v1[rnd()%(i+1)]);
sum[0].pb(-1);
for(int i=0,s=0;i<v1.size();i++)
{
s+=v1[i].fi,s%=n;
int pos=lower_bound(sum[s].begin(),sum[s].end(),i-n)-sum[s].begin();
if(pos<sum[s].size())
{
pos=sum[s][pos];
if(n-i+pos<=v2.size())
{
for(int j=pos+1;j<=i;j++) cout<<v1[j].se<<" ";
for(int j=0;j<n-i+pos;j++) cout<<v2[j].se<<" ";
return;
}
}
sum[s].pb(i);
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【UER #11】企鹅游戏
\(s_i\) 的长度只有 \(O(\sqrt L)\) 种取值,而由于 \(s_i\) 互不相同,每一个 \(t\) 的前缀都只会带来 \(O(\sqrt L)\) 个合法的匹配,建出 \(s_i\) 的 AC 自动机,对于每个状态 \(u\) 求出最近的 fail 树上的祖先 \(nxt_u\),满足这个状态是一个给定的 \(s_i\),将 \(t\) 在 AC 自动机上跑一遍,从每个经过的节点向上跳 \(nxt\),使用光速幂计算答案,复杂度 \(O(L \sqrt L)\)。
一个特别直观的感受是,在所有询问中,出现次数 \(\ge 1\) 次的 \(s_i\) 的个数之和是严重不足 \(O(L\sqrt L)\) 的。所以上述做法有一个很大的优化空间,如果避免重复经过同一个 AC 自动机上的节点,效率会快很多。大致过程是把这些点的虚树拿出来(也不是要真正的建虚树,每个点往上跳,直到跳到根或者跳到一个之前跳到过的点,把所有经过的点拿出来,就是虚树),在虚树上 dfs 一下。但是 uoj 的递归速度很慢,要改成拓扑排序才可以过。
复杂度的话,对于所有长度 \(\le B\) 的串,最多出现 \(L \cdot B\) 次,对于一个长度 \(\gt B\) 的串,总共只有 \(\sqrt{L}-B\) 个,想让其产生复杂度的贡献至少需要长度 \(\gt B\) 的询问串,故最多出现 \(L(\sqrt{L}-B)/B\),令 \(B=L^{1/4}\),复杂度 \(O(L^{5/4})\)。
Code
#pragma GCC optimize("Ofast")
#pragma GCC target("avx")
#include<bits/stdc++.h>
using namespace std;
#define uint unsigned int
#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;
struct ACAM
{
int ch[2000005][26],id[2000005],nxt[2000005],idx;
int fail[2000005];
int add(string s,int x)
{
int nw=0;
for(int i=0;i<s.size();i++)
{
int u=s[i]-'a';
if(!ch[nw][u]) ch[nw][u]=++idx;
nw=ch[nw][u];
}
id[nw]=x;
return nw;
}
queue <int> q;
void build()
{
for(int i=0;i<26;i++) if(ch[0][i]) q.push(ch[0][i]);
while(q.size())
{
int u=q.front();
q.pop();
if(id[u]) nxt[u]=u;
else nxt[u]=nxt[fail[u]];
for(int i=0;i<26;i++)
{
if(ch[u][i]) fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
else ch[u][i]=ch[fail[u]][i];
}
}
// for(int i=0;i<=idx;i++)
// {
// cout<<i<<":\n";
// for(int j=0;j<26;j++) if(ch[i][j]) cout<<j<<" "<<ch[i][j]<<"\n";
// cout<<id[i]<<"\n";
// cout<<nxt[i]<<"\n";
// }
}
}acam;
struct LightPower
{
uint pw1[2000005],pw2[2000005];
int B=(1<<20);
void init()
{
pw1[0]=pw2[0]=1;
for(int i=1;i<=B;i++) pw2[i]=pw2[i-1]*3;
pw1[1]=pw2[B];
for(int i=2;i<=B;i++) pw1[i]=pw1[i-1]*pw1[1];
}
uint query(long long x)
{
return pw1[x>>20]*pw2[x&((1<<20)-1)];
}
}lp;
int n,Q;
int p[2000005],nxt[2000005],to[2000005],eid;
vector <int> vs;
int sum[2000005],tag[2000005],par[2000005],deg[2000005];
bool flg[2000005];
uint ans;
//void dfs(int u)
//{
//// cout<<"dfs: "<<u<<"\n";
// sum[u]=tag[u];
// for(int i=p[u];i;i=nxt[i])
// {
// int v=to[i];
// dfs(v),sum[u]+=sum[v];
// }
// if(acam.id[u]) ans--,ans+=lp.query(1LL*sum[u]*acam.id[u]);
// flg[u]=0,tag[u]=0,p[u]=0;
//}
void solve()
{
lp.init();
cin>>n;
cin>>n>>Q;
for(int i=1;i<=n;i++)
{
string s;
cin>>s;
acam.add(s,i);
}
acam.build();
// cout<<"...\n";
while(Q--)
{
ans=n;
string s;
cin>>s;
int u=0;
queue <int> q;
for(int i=0;i<s.size();i++)
{
int tu=acam.ch[u][s[i]-'a'];
u=tu;
u=acam.nxt[u];
// cout<<"pass: "<<u<<"\n";
sum[u]++;
while(u&&!flg[u])
{
flg[u]=1;
int v=acam.nxt[acam.fail[u]];
// cout<<deg[v]<<"\n";
par[u]=v,deg[v]++;
// cout<<u<<" --- "<<v<<"\n";
// cout<<deg[v]<<"\n";
// eid++,to[eid]=u,nxt[eid]=p[v],p[v]=eid;
// vs.pb(v);
u=v;
}
u=tu;
}
u=0;
for(int i=0;i<s.size();i++)
{
int tu=acam.ch[u][s[i]-'a'];
u=tu;
u=acam.nxt[u];
if(!deg[u]&&u&&flg[u]) flg[u]=0,q.push(u);//,cout<<"init: "<<u<<"\n";
u=tu;
}
// for(int i=0;i<=10;i++) cout<<deg[i]<<" "<<par[i]<<"\n";
// cout<<"\n";
while(q.size())
{
u=q.front();
q.pop();
// if(deg[u]) continue;
if(!u) continue;
if(acam.id[u]) ans--,ans+=lp.query(1LL*sum[u]*acam.id[u]);
sum[par[u]]+=sum[u],deg[par[u]]--;
if(deg[par[u]]==0) q.push(par[u]);
// deg[par[u]]=max(deg[par[u]],0);
sum[u]=flg[u]=0;
// cout<<u<<" "<<par[u]<<" "<<deg[par[u]]<<"\n";
}
// for(int i=1;i<=10;i++) cout<<sum[i]<<" "<<deg[i]<<"\n";
// cout<<"...\n";
// dfs(0),eid=0;
// for(int i=0;i<vs.size();i++) sum[vs[i]]=0,p[vs[i]]=0,flg[vs[i]]=0;
// vs.clear();
cout<<ans<<"\n";
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【候选队互测2022】毛估估就行
一开始的想法是,随机一些点开始 BFS 得到若干 BFS 树,每次询问枚举所有 BFS 树算树上距离取 min,这个表现并不是很好。
不妨抛弃树的想法,BFS 完之后记录每个点到起点的距离 \(d_i\),询问用 \(d_u+d_v\) 估计,输出时将答案 \(-1\) 后输出,观察到如果起点或者起点的邻居在两点的最短路上,答案就是正确的,也就是说一轮 BFS 可以选择度数最大的点,做完之后可以把起点和起点的邻居删除。
BFS 不能进行太多轮,因为询问很多。观察到进行若干轮之后剩下的点和边很少,直接在这些点中暴力 BFS 就行,复杂度玄学。
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;
vector <int> g[8005];
bitset <8015> mask[8015],vis,nw;
int n;
int dist[105][8005];
void bfs(int tid,int u)
{
for(int i=1;i<=n;i++) g[i].clear(),vis[i]=1,dist[tid][i]=inf;
queue <int> q;
q.push(u),vis[u]=0,dist[tid][u]=0;
while(q.size())
{
u=q.front();
q.pop();
nw=vis&mask[u];
for(int i=nw._Find_first();i<=n;i=nw._Find_next(i))
{
vis[i]=0,dist[tid][i]=dist[tid][u]+1;
q.push(i);
}
}
}
int dist2[8005][8005];
void bfs2(int u)
{
int st=u;
for(int i=1;i<=n;i++) vis[i]=1,dist2[u][i]=inf;
queue <int> q;
q.push(u),vis[u]=0,dist2[u][u]=0;
while(q.size())
{
u=q.front();
q.pop();
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(vis[v]==0) continue;
vis[v]=0,dist2[st][v]=dist2[st][u]+1;
q.push(v);
}
}
}
int q;
const int K=80;
int deg[8005];
void delnode(int x)
{
for(int i=1;i<=n;i++) if(mask[x][i])
deg[x]--,deg[i]--,mask[x][i]=mask[i][x]=0;
deg[x]=-1;
}
void solve()
{
cin>>n>>q;
for(int i=2;i<=n;i++)
{
string s;
cin>>s;
for(int j=0,l=1;j<s.size();j++)
{
int x=('0'<=s[j]&&s[j]<='9'?s[j]-'0':10+s[j]-'A');
for(int _=0;_<4;_++)
{
if(x&(1<<_)) mask[i][l]=mask[l][i]=1,deg[i]++,deg[l]++;//,cout<<i<<" --- "<<l<<"\n";
l++;
}
}
}
for(int _=1;_<=K;_++)
{
int mx=1;
for(int i=1;i<=n;i++) if(deg[i]>deg[mx]) mx=i;
bfs(_,mx);
for(int i=1;i<=n;i++) if(mask[i][mx]) delnode(i);
delnode(mx);
}
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(mask[i][j]) g[i].pb(j);
for(int i=1;i<=n;i++) bfs2(i);
while(q--)
{
int u,v;
cin>>u>>v;
int ans=dist2[u][v];
for(int i=1;i<=K;i++) ans=min(ans,dist[i][u]+dist[i][v]);
cout<<ans-1<<"\n";
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【UER #10】随机薅羊毛
令 \(dp(i)\) 表示从 \(i\) 开始抽奖,直到成功的期望次数,可以对于每个 \(dp(i)\) 写出一个方程,\(n\) 个方程 \(n\) 个未知数,可以解了。
但是我们只关注 \(\sum dp(i)\),令 \(x= \sum dp(i)\),在 \(n\) 个方程中用 \(x\) 和 \(p_i\) 去表示 \(dp(i)\),把 \(n\) 个方程加起来,就可以解出 \(x\),复杂度 \(O(n)\)。
Extra test 卡精度,可以用 float128。
Code
#include<bits/stdc++.h>
using namespace std;
#define d128 __float128
#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;
d128 p[1000005];
void solve()
{
cin>>n;
for(int i=1;i<=n;i++)
{
double tmp;
cin>>tmp;
p[i]=(d128)(tmp);
p[i]/=1000000.0;
}
d128 coef_k=-1.0,coef_b=0;
for(int i=1;i<=n;i++)
{
d128 k=(1.0-p[i])/((d128)(n)-p[i]);
d128 b=((d128)(n)-1.0)/((d128)(n)-p[i]);
coef_k+=k,coef_b+=b;
// cout<<k<<" "<<b<<"\n";
}
double ans=(double)((-coef_b)/(coef_k*(d128)(n)));
cout<<fixed<<setprecision(12)<<ans<<"\n";
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int _=1;
// cin>>_;
while(_--) solve();
return 0;
}
【UER #10】重构元宇宙
不妨令第一个点的坐标为 \((0,0,\cdots,0)\),第二个点的坐标为 \((d(1,2),0,\cdots,0)\)。
每次询问 \(d(i,j)\) 可以知道 \(\sum x_i^2+x_j^2-2x_ix_j\),依次加入点,令目前加入点 \(u\),先询问 \(d(u,0)\),得到 \(\sum x_u^2\),然后任意取 \(k\) 个点询问,得到 \(k\) 个方程,可以解出 \(k\) 个坐标(自由元就令其为 \(0\))。观察到排序后就是下三角矩阵,不需要消元,直接解就行。
复杂度 \(O(n^3)\)。
Code
#include "distance.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;
double ans[505][505];
double a[505][505];
int id[505],nowd;
vector <vector<double> > calcans(int n,int k)
{
vector <vector<double> > res;
for(int i=1;i<=n;i++)
{
vector <double> nw;
for(int j=1;j<=k;j++) nw.pb(ans[i][j]);
res.pb(nw);
}
return res;
}
int _query(int x,int y)
{
return query(x-1,y-1);
}
vector <vector<double> > solve(int n,int k,int lim)
{
if(n==1) return calcans(n,k);
for(int i=2;i<=n;i++)
{
if(!nowd)
{
ans[i][1]=(double)(_query(1,i));
ans[i][1]=sqrt(ans[i][1]);
}
else
{
double sq=(double)(_query(1,i));
for(int j=1;j<=nowd;j++)
{
a[j][nowd+1]=(double)(_query(i,id[j]));
a[j][nowd+1]-=sq;
for(int l=1;l<=nowd;l++) a[j][nowd+1]-=ans[id[j]][l]*ans[id[j]][l];
for(int l=1;l<=nowd;l++) a[j][l]=-2.0*ans[id[j]][l];
}
for(int j=1;j<=nowd;j++)
{
double b=a[j][nowd+1];
for(int l=1;l<j;l++) b-=a[j][l]*ans[i][l];
ans[i][j]=b/a[j][j];
}
for(int j=1;j<=nowd;j++) sq-=ans[i][j]*ans[i][j];
if(nowd<k) ans[i][nowd+1]=sqrt(sq);
}
// cout<<nowd<<"\n";
// for(int j=1;j<=k;j++) cout<<ans[i][j]<<" ";
// cout<<"\n";
if(ans[i][nowd+1]) id[++nowd]=i;
}
return calcans(n,k);
}