2022 省队二轮集训培训日记-Day6
模拟赛
T1

考虑树怎么做,直接换根 DP 就可以。考虑仙人掌点双后建圆方树,然后我们主要需要处理方点,DP 的时候我们环上转移时直接把环上的所有点都拿出来,然后转移。换根的时候遇到环要拆贡献。设环上两点 \(i,j\) 的距离为 \(a\) 和 \(len-a\),其中 \(len\) 是环长,那么一个点对另一个点的贡献的系数应该是 \(2^{-a}+2^{-len+a}-2^{-len}\),我们考虑把 \(a\) 拆开,拆成 \(|i-j|\),然后分类套路 \(i< j\) 和 \(i\ge j\) 的情况即可,利用分配率分别统计。
细节较多,注意特判无环转移的情况。
代码:
#include<bits/stdc++.h>
#define mset(a,b) memset((a),(b),sizeof((a)))
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define dec(i,l,r) for(int i=(r);i>=(l);i--)
#define inc(a,b) (((a)+(b))>=mod?(a)+(b)-mod:(a)+(b))
#define sub(a,b) (((a)-(b))<0?(a)-(b)+mod:(a)-(b))
#define mul(a,b) 1ll*(a)*(b)%mod
#define sgn(a) (((a)&1)?(mod-1):1)
#define cmax(a,b) (((a)<(b))?(a=b):(a))
#define cmin(a,b) (((a)>(b))?(a=b):(a))
#define Next(k) for(int x=head[k];x;x=li[x].next)
#define vc vector
#define ar array
#define pi pair
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define N 1000010
#define M number
using namespace std;
typedef double dd;
typedef long double ld;
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
#define int long long
typedef pair<int,int> P;
typedef vector<int> vi;
const int INF=0x3f3f3f3f;
const dd eps=1e-9;
const int mod=998244353;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int dfn[N],low[N],sta[N],top,rt,ctot,tot,n,m,f[N],Pow[N],InvPow[N],inv2,ans[N];
vi dcc[N],e[N],E[N];
inline int ksm(int a,int b,int mod){
int res=1;while(b){if(b&1)res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}return res;
}
inline int inv(int a){return ksm(a,mod-2,mod);}
inline void Tarjan(int k){
dfn[k]=low[k]=++tot;
if(rt==k&&e[k].size()==0){ctot++;dcc[ctot].pb(k);}sta[++top]=k;
for(int to:e[k]){
if(!dfn[to]){
Tarjan(to);low[k]=min(low[k],low[to]);
if(low[to]>=dfn[k]){
ctot++;int x;do{x=sta[top--];dcc[ctot].pb(x);}while(x!=to);dcc[ctot].pb(k);
}
}
else low[k]=min(low[k],dfn[to]);
}
}
inline void Build(){
// printf("ctot=%d\n",ctot);
// rep(i,1,ctot){for(int x:dcc[i]) printf("%d ",x);puts("");}
rep(i,1,ctot){
int now=i+n;
for(int x:dcc[i]){
E[now].pb(x),E[x].pb(now);
// printf("Add %d %d\n",now,x);
}
}
}
inline void DP(int k,int fa){
vi v;v.clear();f[k]++;
// printf("k=%d\n",k);
for(int to:E[k]) if(to!=fa){
// puts("here");
v.clear();
for(int x:E[to]) v.pb(x);
int id=-1;
rep(i,0,(int)v.size()-1) if(v[i]==k) id=i;
rep(i,0,(int)v.size()-1) if(i!=id){
// printf("fa=%d to=%d\n",fa,to);
// printf("k=%d v[i]=%d\n",k,v[i]);
DP(v[i],to);
}
dec(i,0,id-1){
int L=InvPow[id-i],R=InvPow[v.size()-id+i];
if(v.size()!=2) f[k]=(f[k]+1ll*((L+R-InvPow[v.size()])%mod)*f[v[i]]%mod)%mod;
else f[k]=(f[k]+inv2*f[v[i]]%mod)%mod;
}
// printf("k=%d f[k]*Pow[m]=%d\n",k,f[k]*Pow[m]%mod);
rep(i,id+1,(int)v.size()-1){
int L=InvPow[i-id],R=InvPow[v.size()+id-i];
if(v.size()!=2) f[k]=(f[k]+1ll*((L+R-InvPow[v.size()])%mod)*f[v[i]]%mod)%mod;
else f[k]=(f[k]+inv2*f[v[i]]%mod)%mod;
}
}
// printf("k=%d\n",k);
// printf("f[k]*Pow[m]=%d\n",f[k]*Pow[m]%mod);
}
inline void ChRt(int k,int fa){
ans[k]=f[k];vi v;
for(int to:E[k]) if(to!=fa){
v.clear();
for(int x:E[to]) v.pb(x);
//Delete Begin
int id=-1;
rep(i,0,(int)v.size()-1) if(v[i]==k) id=i;
dec(i,0,id-1){
int L=InvPow[id-i],R=InvPow[v.size()-id+i];
if(v.size()!=2) f[k]=(f[k]-1ll*((L+R-InvPow[v.size()])%mod)*f[v[i]]%mod)%mod;
else f[k]=(f[k]-inv2*f[v[i]]%mod)%mod;
}
rep(i,id+1,(int)v.size()-1){
int L=InvPow[i-id],R=InvPow[v.size()+id-i];
if(v.size()!=2) f[k]=(f[k]-1ll*((L+R-InvPow[v.size()])%mod)*f[v[i]]%mod)%mod;
else f[k]=(f[k]-inv2*f[v[i]]%mod)%mod;
}
f[k]=(f[k]+mod)%mod;
//Delete Over
// rep(i,1,3) printf("i=%d f[i]*Pow[m]=%d\n",i,f[i]*Pow[m]);
int sum1=0,sum2=0,sum=0,len=E[to].size();
if(len!=2){
rep(i,0,v.size()-1){
sum=(sum+f[v[i]])%mod;
sum1=(sum1+1ll*f[v[i]]*InvPow[i]%mod)%mod;
sum2=(sum2+1ll*f[v[i]]*InvPow[len-i]%mod)%mod;
}
rep(i,0,v.size()-1){
sum1=(sum1-1ll*f[v[i]]*InvPow[i]%mod)%mod;
sum2=(sum2-1ll*f[v[i]]*InvPow[len-i]%mod)%mod;
sum1=(sum1+1ll*f[v[i]]*InvPow[len+i]%mod)%mod;
sum2=(sum2+1ll*f[v[i]]*Pow[i]%mod)%mod;
if(v[i]==k) continue;
int LastF=f[v[i]];f[v[i]]=(sum1*Pow[i]%mod+sum2*InvPow[i]%mod-sum*InvPow[len]%mod)%mod;
f[v[i]]=(f[v[i]]+mod)%mod;
ChRt(v[i],to);
f[v[i]]=LastF;
}
}
else{
rep(i,0,v.size()-1){
if(v[i]==k) continue;int LastF=f[v[i]];f[v[i]]=(f[v[i]]+f[k]*inv2%mod)%mod;
ChRt(v[i],to);f[v[i]]=LastF;
}
}
//Add Begin
dec(i,0,id-1){
int L=InvPow[id-i],R=InvPow[v.size()-id+i];
if(v.size()!=2) f[k]=(f[k]+1ll*((L+R-InvPow[v.size()])%mod)*f[v[i]]%mod)%mod;
else f[k]=(f[k]+inv2*f[v[i]]%mod)%mod;
}
rep(i,id+1,(int)v.size()-1){
int L=InvPow[i-id],R=InvPow[v.size()+id-i];
if(v.size()!=2) f[k]=(f[k]+1ll*((L+R-InvPow[v.size()])%mod)*f[v[i]]%mod)%mod;
else f[k]=(f[k]+inv2*f[v[i]]%mod)%mod;
}
//Add Over
// rep(i,1,3) printf("i=%d f[i]*Pow[m]=%d\n",i,f[i]*Pow[m]%mod);
}
}
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(m);
inv2=inv(2);Pow[0]=1;rep(i,1,n<<1) Pow[i]=1ll*Pow[i-1]*2%mod;
InvPow[0]=1;rep(i,1,n<<1) InvPow[i]=1ll*InvPow[i-1]*inv2%mod;
rep(i,1,m){
int u,v;read(u);read(v);e[u].pb(v);e[v].pb(u);
}
Tarjan(1);Build();
// exit(0);
DP(1,0);ChRt(1,0);
rep(i,1,n) printf("%d\n",ans[i]*Pow[m]%mod);
return 0;
}
T2


注意对于一个询问,\(e_i\) 互不相同。
\(q\) 只有 \(2000\),这提示我们复杂度可能是 \(qn\) 之类的东西,我们需要用 \(O(n)\) 的时间复杂度去维护每个点的最短路。因为值域很小,所以我们用 \(W\) 个队列去模拟堆,具体来说,我们可以直接在 \(q_i\) 里存放所有最短路为 \(i\) 的点的编号,然后从小往大遍历更新即可,这样复杂度是 \(O(qW)\),但是注意 \(W\) 很大,所以我们不能直接用最短路来做。
那么如果用最短路的增量做会怎样?我们考虑是否有可能增量大的点更新增量小的点,经过简单讨论之后发现没有可能,所以我们可以用增量作为下标来维护每个点的最短路。注意大于下标范围直接就特判掉。复杂度是 \(O(qm)\)。
#include<bits/stdc++.h>
#define mset(a,b) memset((a),(b),sizeof((a)))
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define dec(i,l,r) for(int i=(r);i>=(l);i--)
#define inc(a,b) (((a)+(b))>=mod?(a)+(b)-mod:(a)+(b))
#define sub(a,b) (((a)-(b))<0?(a)-(b)+mod:(a)-(b))
#define mul(a,b) 1ll*(a)*(b)%mod
#define sgn(a) (((a)&1)?(mod-1):1)
#define cmax(a,b) (((a)<(b))?(a=b):(a))
#define cmin(a,b) (((a)>(b))?(a=b):(a))
#define Next(k) for(int x=head[k];x;x=li[x].next)
#define vc vector
#define ar array
#define pi pair
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define N 100010
#define M number
using namespace std;
typedef double dd;
typedef long double ld;
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
#define int long long
typedef pair<int,int> P;
typedef vector<int> vi;
const int INF=1e18;
const dd eps=1e-9;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
struct edge{
int to,next,w;
inline void Init(int to_,int ne_,int w_){
to=to_;next=ne_;w=w_;
}
}li[N<<1];
int head[N],tail,n,m,qu,d[N],ad[N];
queue<int> q[N];
priority_queue<P> Q;
bool vis[N];
inline void Add(int from,int to,int w){
li[++tail].Init(to,head[from],w);head[from]=tail;
}
inline void dij(){
Q.push(mp(0,1));fill(d+1,d+n+1,INF);mset(vis,0);d[1]=0;
while(Q.size()){
P top=Q.top();Q.pop();vis[top.se]=1;
Next(top.se){
int to=li[x].to,w=li[x].w;
if(vis[to]||d[to]<=d[top.se]+w) continue;
d[to]=d[top.se]+w;Q.push(mp(-d[to],to));
}
}
}
inline void Solve(int K){
q[0].push(1);fill(ad+1,ad+n+1,INF);ad[1]=0;
rep(i,0,K){
while(q[i].size()){
int top=q[i].front();q[i].pop();
// printf("top=%d\n",top);
Next(top){
int to=li[x].to,w=li[x].w;
if(ad[to]<=d[top]+ad[top]+w-d[to]) continue;
if(d[top]+ad[top]+w-d[to]>K) continue;
cmin(ad[to],d[top]+ad[top]+w-d[to]);
q[ad[to]].push(to);
// printf("ad[%d]=%d\n",to,ad[to]);
}
}
}
rep(i,1,n) if(d[i]<1e18){
d[i]=d[i]+ad[i];assert(ad[i]<1e18);
}
}
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(m);read(qu);
rep(i,1,m){
int u,v,c;read(u);read(v);read(c);Add(u,v,c);
}dij();
rep(i,1,qu){
// printf("i=%lld\n",i);
int op;read(op);
if(op==1){
int x;read(x);printf("%lld\n",(d[x]<1e18)?d[x]:-1);
}
else{
int k;read(k);rep(i,1,k){int x;read(x);li[x].w++;}Solve(k);
}
}
}
T3


首先贡献都是按照位数计算,很容易想到数位 DP,考虑 \(l_i,r_i\) 一定会有一段前缀相等,在接下来会与 \(l_i\) 或 \(r_i\) 中的某一个相等,在接下来会有一位特殊的数,然后接下来的位数没有限制,如图:

考虑如果有一段区间 \(l,r\),其中 \(l,r\) 的限制是最长的,那么除了 \(l,r\) 区间中其它数都没有的限制可以贪心的填,如图虚线框起来的地方:

否则,我们可以把整个区间分裂开来达到上面的形式。这就非常像笛卡尔树上 DP。写一个递归的数位 DP 就可以。
设 \(dp_{p,l,r,f,x,g,y}\) 表示区间 \(l,r\) 考虑到了第 \(p\) 位,\(f,g\) 表示 \(l,r\) 限制是在左边还是在右边,\(x,y\) 表示是否为特殊的那一位。我们限制 \(l,r\) 一定是有限制的,否则我们把它们置为 \(0\) 和 \(n+1\)。
代码:
#include<bits/stdc++.h>
#define mset(a,b) memset((a),(b),sizeof((a)))
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define dec(i,l,r) for(int i=(r);i>=(l);i--)
#define inc(a,b) (((a)+(b))>=mod?(a)+(b)-mod:(a)+(b))
#define sub(a,b) (((a)-(b))<0?(a)-(b)+mod:(a)-(b))
#define mul(a,b) 1ll*(a)*(b)%mod
#define sgn(a) (((a)&1)?(mod-1):1)
#define cmax(a,b) (((a)<(b))?(a=b):(a))
#define cmin(a,b) (((a)>(b))?(a=b):(a))
#define Next(k) for(int x=head[k];x;x=li[x].next)
#define vc vector
#define ar array
#define pi pair
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define N 51
#define M number
using namespace std;
typedef double dd;
typedef long double ld;
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
#define int long long
typedef pair<int,int> P;
typedef vector<int> vi;
const int INF=0x3f3f3f3f;
const dd eps=1e-9;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int dp[N][N][N][2][2][2][2],n,k,L[N][2],C[N];
inline int bit(int x,int p){return (x>>p)&1;}
inline int dfs(int p,int l,int r,int f,int x,int g,int y){
if(p==k) return (r-l+1==2)?0:1e18;
int &now=dp[p][l][r][f][x][g][y];
if(now!=-1) return now;
now=1e18;now=dfs(p+1,l,r,f,0,g,0)+(l&&r<=n&&(x^y^bit(L[l][f]^L[r][g],p))==1)*C[p];
rep(k,l+1,r-1){
if(!p) rep(j,0,1) cmin(now,dfs(p,l,k,f,x,j,0)+dfs(p,k,r,j,0,g,y));
if((L[k][0]^L[k][1])>>(p+1)) rep(j,0,1) if(bit(L[k][j],p)==j) cmin(now,dfs(p,l,k,f,x,j,1)+dfs(p,k,r,j,1,g,y));
}
// printf("%lld %lld %lld %lld %lld %lld %lld=%lld\n",p,l,r,f,x,g,y,now);
return now;
}
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(k);
rep(i,1,n){
read(L[i][0]);read(L[i][1]);
}
mset(dp,-1);
rep(i,0,k-1) read(C[i]);
int Ans=dfs(0,0,n+1,0,0,0,0);
printf("%lld\n",Ans);
return 0;
}
题目
CF1408G
考虑从小到大加入每条边,那么一个集合 \(S\) 可能作为某个划分方案的一部分,当且仅当在加边过程中,存在一个连通块,它是一个团,且它的点集是 \(S\)。
如果不考虑团的限制,我们可以直接建出克鲁斯卡尔重构树的结构,并且这个树的点数是 \(O(n)\) 的,团的限制很好往上加,我们只需要在建树的时候考虑每个连通块是否为团,然后判断每个点是否可行。最后的答案相当于在这棵树上选 \(k\) 个点,满足这 \(k\) 个点都可行,且选完这 \(k\) 个点之后,不存在一条从根到叶子的路径不经过这 \(k\) 个点,并且这 \(k\) 个点不存在两个点是祖先父亲关系。树形背包即可。
复杂度瓶颈在排序,\(O(2n^2\log n)\)
struct Line{
int u,v,w;
inline Line(){}
inline Line(int u,int v,int w) : u(u),v(v),w(w) {}
inline bool operator < (const Line &b)const{return w<b.w;}
}li[N*N];
int n,a[N][N],lt,fa[N<<1],siz[N<<1],en[N<<1],f[N<<1][N<<1],g[N<<1];
vi e[N<<1];
bool vis[N<<1];
inline int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
inline void Merge(int a,int b){
if(siz[a]==1){
rep(i,1,siz[b]) f[a][i]=f[b][i];return;
}
mset(g,0);
rep(i,0,siz[a]+siz[b])rep(j,max(i-siz[b],0),siz[a]) g[i]=(g[i]+1ll*f[a][j]*f[b][i-j]%mod)%mod;
g[0]=0;rep(i,1,siz[a]+siz[b]) f[a][i]=g[i];
}
inline void dfs(int k,int fa){
siz[k]=1;
for(int to:e[k]) if(to!=fa){
dfs(to,k);Merge(k,to);siz[k]+=siz[to];
}
if(vis[k]) f[k][1]=1;else f[k][1]=0;
// printf("k=%d\n",k);
// rep(i,1,siz[k]) printf("%d ",f[k][i]);puts("");
}
inline void Add(int a,int b){
printf("Add %d %d\n",a,b);
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);rep(i,1,n)rep(j,1,n) read(a[i][j]);
rep(i,1,n)rep(j,i+1,n) li[++lt]=Line(i,j,a[i][j]);
sort(li+1,li+lt+1);
int tot=n;rep(i,1,n) vis[i]=1;rep(i,1,n<<1) en[i]=0,siz[i]=1,fa[i]=i;
rep(i,1,lt){
int u=li[i].u,v=li[i].v;
int fau=Find(u),fav=Find(v);
if(fau==fav){
en[fau]++;if(en[fau]==siz[fau]*(siz[fau]-1)/2) vis[fau]=1;
continue;
}
++tot;fa[fau]=tot;fa[fav]=tot;e[tot].pb(fau);e[tot].pb(fav);
// Add(tot,fau);Add(tot,fav);
siz[tot]=siz[fau]+siz[fav];en[tot]=en[fau]+en[fav]+1;
if(en[tot]==siz[tot]*(siz[tot]-1)/2) vis[tot]=1;
}
// printf("tot=%d\n",tot);
// rep(i,1,tot) printf("vis[%d]=%d\n",i,vis[i]);
// exit(0);
mset(siz,0);dfs(tot,0);
rep(i,1,n) printf("%d ",f[tot][i]);
return 0;
}
CF1383E
首先两边的 \(0\) 都是可以去掉的,特判掉没有 \(1\) 和只有一个 \(1\) 的情况,现在问题变成了一个两端都是 \(1\) 的长度大于 \(1\) 的字符串。我们考虑把相邻两个 \(1\) 之间的 \(0\) 的个数取出来,那么问题相当于问我们有多少个序列满足可以找到 \(a\) 的一个子序列,对应位置都不比这个序列小。设 \(f_i\) 表示以 \(i\) 结尾的序列个数,那么我们枚举这里填的数字 \(x\),我们需要找到一个最大的 \(k\) 满足 \(a_k\ge x\),然后作的贡献为 \(\sum_{j=k}^{i-1}f_j\),考虑如何快速找到 \(k\),用线段树可以 \(\log n\),但是可以用单调栈来替换,我们可以从小到大枚举 \(x\),这样 \(k\) 是单调向左移动的,在单调栈上,指针移动的步数相当于把 \(a_i\) 放进去的弹栈次数,枚举 \(x\) 是 \(O(n)\) 的,前面指针的移动也是 \(O(n)\) 的,所以最后的复杂度是 \(O(n)\)。
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
scanf("%s",s+1);int len=strlen(s+1);
int l=1;while(s[l]=='0'&&l<len) l++;
int r=len;while(s[r]=='0'&&l<=r) r--;
// printf("l=%d r=%d\n",l,r);
if(l>r){
printf("%lld\n",len);return 0;
}
if(l==r){
l=l-1;r=len-r;
printf("%lld\n",(l+1)*(r+1)%mod);return 0;
}
int nl=l;
while(nl<r){
int cnt=0;nl++;while(s[nl]!='1') cnt++,nl++;
a[++at]=cnt;
}
l=l-1;r=len-r;
// printf("nowl=%lld nowr=%lld\n",l,r);
n=at;
f[1]=a[1]+1;sum[1]=a[1]+2;sum[0]=1;f[0]=1;
sta[++top]=1;
// rep(i,1,n) printf("a[%lld]=%lld\n",i,a[i]);
// printf("n=%d\n",n);
rep(i,2,n){
int l=top;
rep(j,0,a[i]){
while(l&&a[sta[l]]<j) l--;
// printf("j=%d l=%d\n",j,l);
f[i]=(f[i]+sum[i-1]-(l==0?0:sum[sta[l]-1]))%mod;
}
sum[i]=(sum[i-1]+f[i])%mod;
while(top&&a[sta[top]]<=a[i]) top--;sta[++top]=i;
}
// rep(i,1,n) printf("f[%d]=%lld\n",i,f[i]);
int Ans=0;
rep(i,1,n) Ans=(Ans+f[i])%mod;Ans++;
printf("%lld\n",(Ans*(l+1)%mod*(r+1)%mod+mod)%mod);
return 0;
}
CF1372E
首先考虑如果有一列要放 \(1\) 的话,一定是放满较优的,这个可以通过讨论得到,那么对于原问题,我们可以枚举一列,然后剩下的就是两个子问题,这引导我们区间 DP。
只需要预处理出 \(g_{l,r,k}\) 表示考虑 \(l,r\) 内的区间,\(k\) 的位置如果要放的话,最多能放多少个,这个可以用扫描线在 \(O(n^3)\) 的时间复杂度内做到,具体看代码。
然后区间 DP 就可以了。
int n,m,f[N][N],g[N][N][N],L[N][N],R[N][N],b[N];
vi v[N];
inline int F(int x){return x*x;}
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(m);
rep(i,1,n){
int K;read(K);
rep(j,1,K){
int l,r;read(l);read(r);v[r].pb(l);
}
}
rep(i,1,m){
mset(b,0);
rep(j,1,m){
for(int x:v[j]) if(x>=i){
rep(k,x,j) b[k]++;
}
int ans=-INF;
rep(k,i,j) g[i][j][k]=b[k];
}
}
// rep(i,1,m)rep(j,i,m)rep(k,i,j) printf("g[%lld][%lld][%lld]=%lld\n",i,j,k,g[i][j][k]);
rep(i,1,m) f[i][i]=F(g[i][i][i]);
rep(i,2,m){
rep(j,1,m-i+1){
int l=j,r=j+i-1;
rep(k,l,r)cmax(f[l][r],f[l][k-1]+f[k+1][r]+F(g[l][r][k]));
}
}
printf("%lld\n",f[1][m]);
return 0;
}
CF1366G
DP 状态还是非常显然的,经典模型:设 \(f_{i,j}\) 表示 \(s\) 串匹配到 \(i\),\(t\) 串匹配到 \(j\),最优解是多少。一共有三种转移:
其中转移 \(1\) 需要满足 \(s_{i+1}=t_{j+1}\),转移 \(3\) 需要满足 \(i+1\) 到 \(i+len\) 这一段的字符是可以自己消去的。转移 \(1,2\) 都好说,重点是 \(3\),我们考虑可以预处理所有可以自然消去的区间,一共有 \(n^2\) 个,这样转移可能变成 \(O(n^3)\),而我们发现这样的区间满足可差分性和可合并性,观察后发现互相包含的区间较大的那个是没有意义的,所以区间个数被我们减成了 \(O(n)\) 个,转移即可。
char s[N],t[N];
int lens,lent,f[N][N];
vi v[N];
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
scanf("%s",s+1);scanf("%s",t+1);int lens=strlen(s+1),lent=strlen(t+1);
int cnt=0;
rep(i,1,lens){
cnt=0;
rep(j,i,lens){
if(s[j]=='.') cnt--;else cnt++;if(cnt<0) break;
if(cnt==0) {v[i].pb(j);break;}
}
}
mset(f,INF);
f[0][0]=0;
rep(i,0,lens)rep(j,0,lent)if(f[i][j]!=INF){
if(s[i+1]==t[j+1]) cmin(f[i+1][j+1],f[i][j]);
cmin(f[i+1][j],f[i][j]+1);
for(int x:v[i+1]) cmin(f[x][j],f[i][j]);
}
// rep(i,0,lens)rep(j,0,lent)printf("f[%d][%d]=%d\n",i,j,f[i][j]);
printf("%d\n",f[lens][lent]);
return 0;
}
CF1342F
题意相当于让我们选择一个划分,满足这个划分的集合尽量多,且满足每个集合在内部选出一个下标,然后随着各个集合下标递增,集合内元素和也严格递增。
显然要记录选了那些下标,考虑不能记录元素和,我们就记录选的集合个数,然后让元素和最小作为我们要求的就行了,发现下表也有限制,我们也放在状态里,设 \(f_{S,i,j}\) 表示已经选了 \(S\),选了 \(i\) 个集合,最后一个集合的下标为 \(j\),然后枚举下一个集合选什么即可。复杂度 \(O(3^n n^2)\)
int t,n,a[N],f[M][N][N],b[M],sum[M],tot;
vi v[N];
P g[M][N][N];
inline int lowbit(int x){return x&(-x);}
inline void Solve(int i,int j,int k){
if(!i||!j||!k) return;
P now=g[i][j][k];
int T=now.fi^i;rep(o,0,n-1) if(((T>>o)&1)&&o+1!=k){
v[k].pb(o+1);
// printf("k=%d i=%d\n",k,o+1);
}
Solve(now.fi,j-1,now.se);
}
inline int ID(int x){
int cnt=0;rep(i,1,x) if(a[i]) cnt++;return cnt;
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
rep(i,0,15) b[(1<<i)]=i;
read(t);
while(t--){
read(n);rep(i,1,n) read(a[i]);
rep(i,0,(1<<n)-1)rep(j,0,n)rep(k,0,n) f[i][j][k]=INF,g[i][j][k]=mp(-1,-1);
rep(i,0,(1<<n)-1) sum[i]=0;
rep(i,0,15) v[i].clear();
f[0][0][0]=0;
rep(i,0,(1<<n)-1)rep(j,0,n-1) if((i>>j)&1) sum[i]+=a[j+1];
rep(i,0,(1<<n)-1)rep(j,0,n)rep(k,0,n)if(f[i][j][k]!=INF){
// printf("i=%d j=%d k=%d\n",i,j,k);
int S=((1<<n)-1)^i;
for(int T=S;T;T=S&(T-1)){
// printf("T=%d\n",T);
// printf("T>>k=%d\n",(T>>k));
if(!(T>>k)) continue;
// printf("T=%d\n",T);
if(sum[T]<=f[i][j][k]) continue;
int tt=T>>(k);int posi=b[lowbit(tt)];
// cmin(f[i|T][j+1][posi+(k)+1],sum[T]);
if(f[i|T][j+1][posi+k+1]>sum[T]){
f[i|T][j+1][posi+k+1]=sum[T];g[i|T][j+1][posi+k+1]=mp(i,k);
}
// g[i|T][j+1][posi+k+1]=mp(i,k);
// printf("f[%d][%d][%d]=%d\n",i|T,j+1,posi+k+1,f[i|T][j+1][posi+k+1]);
}
}
int ans=-1;tot=0;
bool op=0;
dec(i,1,n){
rep(j,1,n) if(f[(1<<n)-1][i][j]!=INF){
Solve((1<<n)-1,i,j);
ans=i;op=1;break;
}
if(op) break;
}
// printf("ans=%d\n",ans);
printf("%d\n",n-ans);
rep(i,1,n){
if(!v[i].size()) continue;
for(int x:v[i]){
int nowi=ID(x);int nowj=ID(i);
printf("%d %d\n",nowi,nowj);a[x]=0;
}
}
}
return 0;
}
CF1326F2
值得一提的是,上面的题解是我见过的这个题目的题解中最清晰易懂的一篇。
首先限制过强,首先考虑容斥,设 \(F_S\) 表示串中 \(S\) 中的 \(0\) 表示认识或不认识,\(1\) 表示认识的方案数,设 \(ans_S\) 表示答案,那么我们显然有:
求 \(ans\) 相当于要做这个的逆变换,也相当于做 FWT 并卷积的逆变换,或者是做 FMT。可以做到 \(O(n2^n)\)
考虑求 \(F\)。发现当限制弱化之后,\(0\) 相当于给排列分段,每一个字符串都可以看成一个可重集,而可重集相同,\(F\) 值也一定相同。可重集最多有 \(385\) 个,所以现在考虑对于每个可重集算一下 \(F\) 的答案。
设当前的可重集为 \(a_1,a_2,\cdots,a_k\),注意一定有 \(\sum_{i=1}^ka_i=n\),我们要解决的问题相当于要把原来的点集划分成 \(k\) 个点集,设为 \(S_1,S_2,\cdots,S_k\),其中满足 \(S_i=a_i,S_i\cap S_j=\varnothing,S_1\cup S_2\cdots\cup S_k=\{1,2,\cdots n\}\)。设 \(g_S\) 表示把 \(S\) 中的点排成排列,相邻两个点相连的方案数。那么我们相当于求:
考虑第二个限制和第三个限制满足一个即可,我们把第二个限制去掉。现在考虑如果没有第一个限制,那么我们直接把 \(g\) FWT 出来,然后每个位置求个 \(k\) 次幂即可。现在考虑如何加上第一个限制,我们按照子集卷积的套路,设一个 \(g_{a_i,S_i}\) 即可,其中 \(a_i\) 是 \(S_i\) 的大小。
而 \(g_S\) 怎么求,我们可以设计一个 \(O(n^22^n)\) 的 DP,设 \(G_{S,i}\) 表示经过的点集为 \(S\),结尾为 \(i\) 的方案数。转移即可。
求出来之后,直接 FWT 掉,然后枚举可重集直接乘,而因为我们只需要一个位置的值,可以利用或卷积通项公式在 \(O(2^n)\) 的时间内求出。
用哈希完成从可重集到整数的映射即可。
总时间复杂度 \(O((385+n^2)2^n)\)
int G[N][M],g[M][N],n,cou[N],tmp[N],F[N],ans[N];
char s[M];
bool K[M][M];
map<ll,ll> Hash;
inline void FWTor(int *f,int op,int n){
for(int i=2;i<=n;i<<=1)
for(int j=0;j<n;j+=i)
for(int k=j;k<j+(i/2);k++){
if(op){
f[k+i/2]+=f[k];
}
else f[k+i/2]-=f[k];
}
}
inline void FWTand(int *f,int op,int n){
for(int i=2;i<=n;i<<=1)
for(int j=0;j<n;j+=i)
for(int k=j;k<j+i/2;k++){
if(op) f[k]+=f[k+i/2];
else f[k]-=f[k+i/2];
}
}
vi now;
inline void dfs(int sum,int lst){
if(!sum){
rep(i,0,(1<<n)-1) tmp[i]=1;
for(int x:now)rep(i,0,(1<<n)-1) tmp[i]*=g[x][i];
int S=(1<<n)-1,ans=0;
for(int T=S;T;T=S&(T-1)){
if((cou[S]-cou[T])&1) ans-=tmp[T];
else ans+=tmp[T];
}
int ha=0;
for(int x:now) ha=(ha*base%mod+x)%mod;Hash[ha]=ans;
// for(int x:now) printf("%lld ",x);printf("ha=%lld\n",ha);
// printf("Hash[%lld]=%lld\n",ha,ans);
return;
}
for(int i=lst;i<=sum/2;i++){
now.pb(i);dfs(sum-i,i);now.pop_back();
}
now.pb(sum);dfs(0,sum);now.pop_back();
}
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);
rep(i,0,n-1){
scanf("%s",s);
rep(j,0,n-1) K[i][j]=(s[j]=='1');
}
rep(i,0,n-1) G[1<<i][i]=1;
rep(i,0,(1<<n)-1)rep(j,0,n-1)if(G[i][j]){
rep(k,0,n-1) if(K[j][k]&&!((i>>k)&1)) G[i|(1<<k)][k]+=G[i][j];
}
rep(i,0,(1<<n)-1) cou[i]=cou[i>>1]+(i&1);
// rep(i,0,(1<<n)-1) rep(j,0,n-1) printf("G[%d][%d]=%d\n",i,j,G[i][j]);
rep(i,0,(1<<n)-1) rep(j,0,n-1) g[cou[i]][i]+=G[i][j];
// rep(i,0,(1<<n)-1) printf("g[%d][%d]=%d\n",cou[i],i,g[cou[i]][i]);
// rep(i,0,n){printf("i=%d ",i);rep(j,0,(1<<n)-1) printf("%d ",g[i][j]);puts("");}
rep(i,0,n){
// printf("i=%d\n",i);
FWTor(g[i],1,(1<<n));
}
// rep(i,0,n){printf("i=%d ",i);rep(j,0,(1<<n)-1) printf("%d ",g[i][j]);puts("");}
dfs(n,1);
rep(i,0,(1<<(n-1))-1){
// printf("i=%d\n",i);
int l=0,r=0;
vi now;now.clear();
while(l<=n-1){
r=l;while(r<=(n-2)&&(i>>r)&1) r++;
now.pb(r-l+1);l=r+1;
}
sort(now.begin(),now.end());
// for(int x:now) printf("%d ",x);puts("");
int ha=0;
for(int x:now) ha=(ha*base%mod+x)%mod;
F[i]=Hash[ha];
}
FWTand(F,0,(1<<(n-1)));
rep(i,0,(1<<(n-1))-1) printf("%lld ",F[i]);
return 0;
}
P4151
求无向连通图从 \(1\) 到 \(n\) 的路径(不一定是简单路径)异或和最大值是多少。
首先考虑如果一条边已经固定下来了,我们可以随意选择不与这条边相交的环,而实际上显而易见的是,我们可以把所有的环都塞进线性基里,然后随意的选择一条路径。因为路径的改变相当于原路径异或上一个环。
int n,m;
struct edge{
int to,next,w;
inline void Init(int to_,int ne_,int w_){
to=to_;next=ne_;w=w_;
}
}li[N<<1];
int head[N],tail,Ans,d[N];
bool vis[N];
struct LinearBasis{
int p[70];
inline bool Insert(int x){
dec(i,0,63) if((x>>i)&1){if(!p[i]){p[i]=x;return 1;}else x^=p[i];}return 0;
}
inline int Ask(int x){
int ans=x;dec(i,0,63) if((ans^p[i])>ans) ans^=p[i];return ans;
}
}lb;
inline void Add(int from,int to,int w){
li[++tail].Init(to,head[from],w);head[from]=tail;
}
inline void dfs(int k,int fat,int xo){
vis[k]=1;if(k==n) Ans=xo;d[k]=xo;
Next(k){
int to=li[x].to;if(to==fat) continue;
if(!vis[to]) dfs(to,k,xo^li[x].w);
else lb.Insert(xo^li[x].w^d[to]);
}
}
signed main(){
// freopen("my.in","r",stdin);
read(n);read(m);
rep(i,1,m){
int u,v,w;read(u);read(v);read(w);Add(u,v,w);Add(v,u,w);
}
dfs(1,0,0);
Ans=lb.Ask(Ans);
printf("%lld\n",Ans);
return 0;
}
CF1299D
首先不考虑三元环,假设不存在包含 \(1\) 的环,假如 \(1\) 度数为 \(1\),判断一条路径是否异或和为 \(0\) 且存在一条边经过奇数次只需要看这个节点对应的那一个子图中所有的环边权异或和是否能够异或出 \(0\),用线性基即可。度数不为 \(1\) 只需要考虑线性基的合并能否异或出 \(0\)。如果我们对所有可能的线性基进行编号,可以得到一个 DP,设 \(f_{i,j}\) 表示考虑前 \(i\) 个点,满足限制,且当前异或出的线性基为 \(j\),最小删掉的边,转移只需要考虑第 \(i\) 个是否删,如果不删的话需要满足线性基合并不能异或出 \(0\)。
加上三元环,转移变得略微复杂,但整体思路不变。
这里在下面放上我为了调试写的数据生成器。
int n,m,tot,ID[M],IID[500];
struct edge{
int to,next,w;
inline void Init(int to_,int ne_,int w_){
to=to_;next=ne_;w=w_;
}
}li[N<<1];
int head[N],tail,f[N][376];
#define LinearBasis LB
struct LinearBasis{
int p[5];
inline bool Insert(int x){
if(x==0) return 1;
dec(i,0,4) if((x>>i)&1){
if(p[i]){x^=p[i];}
else{
p[i]=x;
rep(j,0,i-1) if(p[j]&&((p[i]>>j)&1)) p[i]^=p[j];
rep(j,i+1,4) if(p[j]&&((p[j]>>i)&1)) p[j]^=p[i];return 1;
}
}
return 0;
}
inline int Hash(){return p[0]|(p[1]<<1)|(p[2]<<3)|(p[3]<<6)|(p[4]<<10);}
inline void IHash(int x){
int now=1;
rep(i,0,4){
p[i]=x&((1<<now)-1);
x>>=now;now++;
}
}
}A,lb[N];
bool tag[N],vis[N],vi[N],ok[N];
int W[N];
int d[N],ow[N];
map<P,int> Map;
inline void Add(int from,int to,int w){
li[++tail].Init(to,head[from],w);head[from]=tail;
}
inline void dfs(int k){
LB now=A;
if(k==32){
if(ID[A.Hash()]) return;ID[A.Hash()]=++tot;IID[tot]=A.Hash();return;
}
dfs(k+1);A=now;if(A.Insert(k)) dfs(k+1);A=now;
}
inline void Dfs(int k,int fa,int xo,int id){
// printf("k=%d\n",k);
vis[k]=1;d[k]=xo;Next(k){
int to=li[x].to,w=li[x].w;
if(to==fa) continue;
if(tag[to]&&tag[k]){vi[k]=1;vi[to]=1;ow[k]=to;ow[to]=k;Map[mp(to,k)]=Map[mp(k,to)]=w;continue;}
if(!vis[to]) Dfs(to,k,xo^w,id);
else{
// printf("k=%d to=%d\n",k,to);
if(k<to) continue;
// printf("Insert %d\n",d[to]^xo^w);printf("d[%d]=%d xo=%d w=%d\n",to,d[to],xo,w);
if(!lb[id].Insert(d[to]^xo^w)||!(d[to]^xo^w)){ok[id]=0;}
}
}
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(m);dfs(1);
//tot=374
rep(i,1,m){
int u,v,w;read(u);read(v);read(w);Add(u,v,w);Add(v,u,w);
}
Next(1){
int to=li[x].to;tag[to]=1;ok[to]=1;W[to]=li[x].w;
}
Next(1){
int to=li[x].to;Dfs(to,1,li[x].w,to);
// printf("ok[%d]=%d\n",to,ok[to]);
}
int last=0;mset(f,0);f[0][1]=1;
for(int x=head[1];x;x=li[x].next){
int to=li[x].to;
if(!vi[to]){
rep(j,1,374) f[to][j]=(f[to][j]+f[last][j])%mod;
if(ok[to])rep(j,1,374)if(f[last][j]){
LB now;now.IHash(IID[j]);
bool op=1;
rep(k,0,4) if(!now.Insert(lb[to].p[k])){op=0;break;}
if(!op) continue;
f[to][ID[now.Hash()]]=(f[to][ID[now.Hash()]]+f[last][j])%mod;
}
}
else{
if(ow[to]<to) continue;
// printf("to=%d\n",to);
int a=to,b=ow[to],w=Map[mp(a,b)];
rep(j,1,374) f[a][j]=(f[a][j]+f[last][j])%mod;
LB now=lb[a];
bool op=1;
rep(j,0,4) if(!now.Insert(lb[b].p[j])){op=0;break;}
if(op&&ok[a]&&ok[b])rep(j,1,374)if(f[last][j]){
LB nowj;nowj.IHash(IID[j]);
bool op2=1;
rep(k,0,4) if(!nowj.Insert(now.p[k])){op2=0;break;}
if(!op2) continue;
f[a][ID[nowj.Hash()]]=(f[a][ID[nowj.Hash()]]+1ll*f[last][j]*2%mod)%mod;
}
// printf("op=%d\n",op);
op&=now.Insert(W[a]^W[b]^w);
// printf("op=%d\n",op);
op&=(bool)(W[a]^W[b]^w);
// printf("op=%d w=%d W[%d]=%d W[%d]=%d\n",op,w,a,W[a],b,W[b]);
if(op&&ok[a]&&ok[b])rep(j,1,374)if(f[last][j]){
LB nowj;nowj.IHash(IID[j]);
bool op2=1;
rep(k,0,4) if(!nowj.Insert(now.p[k])){op2=0;break;}
if(!op2) continue;
f[a][ID[nowj.Hash()]]=(f[a][ID[nowj.Hash()]]+f[last][j])%mod;
}
}
last=to;
// rep(j,1,374) printf("f[%d][%d]=%d\n",last,j,f[last][j]);
}
int ans=0;
rep(i,1,374) ans=(ans+f[last][i])%mod;
printf("%d\n",ans);
return 0;
}
gen:
#include<bits/stdc++.h>
#define mset(a,b) memset((a),(b),sizeof((a)))
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define dec(i,l,r) for(int i=(r);i>=(l);i--)
#define cmax(a,b) (((a)<(b))?(a=b):(a))
#define cmin(a,b) (((a)>(b))?(a=b):(a))
#define Next(k) for(int x=head[k];x;x=li[x].next)
#define vc vector
#define ar array
#define pi pair
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define N 1000010
#define M number
using namespace std;
typedef double dd;
typedef long double ld;
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
//#define int long long
typedef pair<int,int> P;
typedef vector<int> vi;
const int INF=0x3f3f3f3f;
const dd eps=1e-9;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
struct edge{
int u,v,w;
};
vc<edge> e;
vi v[N];
int n,m,fa[N];
int c[N];
map<P,int> vis;
bool tag[N];
bool t2[N];
inline int random(int n){return 1ll*rand()*rand()%n+1;}
inline void dfs(int k,int id){
c[k]=id;
for(int to:v[k]){
dfs(to,id);
}
}
int main(){
srand(time(0));
freopen("my.in","w",stdout);
int n=10;
rep(i,2,n) fa[i]=random(i-1),e.pb({fa[i],i,random(32)-1}),v[fa[i]].pb(i),vis[mp(fa[i],i)]=vis[mp(i,fa[i])]=1;
int cnt=0;
for(int to:v[1]){
cnt++;tag[to]=1;
dfs(to,cnt);
}
int m=n-1;
int x=4;t2[1]=1;
rep(i,1,x){
int u,v;
do{
u=random(n),v=random(n);
}while(!((c[u]==c[v]||(tag[u]&&tag[v]))&&vis[mp(u,v)]==0&&(!t2[u]&&!t2[v])&&u!=v));
vis[mp(u,v)]=vis[mp(v,u)]=1;
if(tag[u]&&tag[v]) t2[u]=t2[v]=1;
e.pb({u,v,random(32)-1});
}
m+=x;
printf("%d %d\n",n,m);
for(edge now:e){
printf("%d %d %d\n",now.u,now.v,now.w);
}
return 0;
}

浙公网安备 33010602011771号