作为一名高二老年选手来补一下我省去年的省选题。

D1T1:寻宝游戏

按顺序给出\(n\)个\(m\)位的二进制数\(a_i\),再在最前方添一个\(0\),
给出\(q\)次询问,每次询问给出一个同样长为\(m\)的二进制数\(r_i\),
要求在之前给出的\(n+1\)个二进制数的每相邻两个数的空位添加按位与运算符按位或运算符,
一共\(n\)个,并使得这个算式得到的值为\(r_i\),求方案数。
\(n,q\le 1000,m\le 5000\)

暴力\(30\%\)不提。

对每一位分开考虑。
根据 [NOI2014]起床困难综合症 的理论或者自己手推,我们可以得到如下结论:
考虑\(r_i\)的第\(j\)位和在其之前的\(n\)个\(bit\),可以知道如果\(|0\)或者\(\&1\)对结果没有影响;
如果这一位上是\(1\),那么情况应该是在最后一个\(|1\)后不存在\(\&0\);
如果这一位上是\(0\),那么情况应该是在最后一个\(\&0\)后不存在\(|1\),或者既没有\(\&0\)也没有\(|1\)。

你看这里的条件都和最后一次进行的操作相关,所以我们倒着确定每一次放入的符号:
我们来看一看\(a_n\)和\(r_i\)对应位置上的不同情况。
\(x?0=0,x?1=1\) : \(\&\ |\)均可。
\(x?0=1\):如果放入\(\&\)运算符则结果必定为\(0\),因此这个运算符只能为\(|\);
\(x?1=0\):如果放入\(|\)运算符则结果必定为\(1\),因此这个运算符只能为\(\&\)。
综上所述,我们可以得出:

如果\(a_n\)和\(r_i\)同时出现了\(0-1,1-0\)两种情况,显然无解;
否则,如果\(a_n\)和\(r_i\)有任何一位不同\((0-1/1-0)\),那么运算符是可以唯一确定的;
此时将需要考虑的行减少一些并继续考虑\(a_{n-1}\);
否则,可以知道此时\(a_n=r_i\);
此时要分\(r_i\)是否含有\(0/1\)进行讨论:
如果\(r_i\)全为\(0\),那么在\(a_n\)前添加\(\&\)后,\(a_{1-n-1}\)之前的运算符可以随机添加;
答案加上\(2^{n-1}\),然后递归考虑添加\(|\)的情况;
\(r_i\)全为\(1\)同理。
如果\(r_i\)即有\(1\)又有\(0\),只能分别进行递归。
但是,在这一次递归之后仅需考虑\(r_i\)剩下的全为\(1\)的部分 或 剩下的全为\(0\)的部分,
这意味着之后不会再次出现这种情况。
使用\(bitset\)优化运算,复杂度为\(O(\frac{nmq}{32})\),可以通过\(70\%\)的数据点。O2就过了

#include<bits/stdc++.h>
#define FL "a"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=1e3+10;
const int mod=1e9+7;
inline ll read(){ll data=0,w=1;char ch=getchar();while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();if(ch=='-')w=-1,ch=getchar();while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();return data*w;
}
inline void file(){freopen(FL".in","r",stdin);freopen(FL".out","w",stdout);
}
inline void upd(int &a,int b){a+=b;if(a>=mod)a-=mod;}
inline void dec(int &a,int b){a-=b;if(a<0)a+=mod;}
inline int poww(int a,int b){int res=1;for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)res=1ll*a*res%mod;return res;
}
int n,m,q,ans,pw[1005];char s[5005];
typedef bitset<5000> line;
line a[N],b[N],r,rv,e[N],f[N],u,tmp;
inline void solve(int x,line now){if(!x){if(!(r&now).any())upd(ans,1);return;}int c=(e[x]&now).any(),d=(f[x]&now).any();if(!c&&!d){if((r&now).any()&&(rv&now).any())solve(x-1,now&a[x]),solve(x-1,now&b[x]);else upd(ans,pw[x-1]),solve(x-1,now);}if(!c&&d)solve(x-1,now&b[x]);if(c&&!d)solve(x-1,now&a[x]);
}
int main()
{n=read();m=read();q=read();register int i,j;for(i=0;i<m;i++)u.set(i);for(i=pw[0]=1;i<=n;i++)pw[i]=2ll*pw[i-1]%mod;for(i=1;i<=n;i++){scanf("%s",s+1);for(j=1;j<=m;j++)if(s[j]=='1')a[i].set(j-1);b[i]=u^a[i];}for(i=1;i<=q;i++){scanf("%s",s+1);r.reset();for(j=1;j<=m;j++)if(s[j]=='1')r.set(j-1);rv=u^r;for(j=1;j<=n;j++){tmp=a[j]^r;e[j]=tmp&a[j];f[j]=tmp&r;}ans=0;solve(n,u);printf("%d\n",ans);}return 0;
}

考虑再次进行转化。发现分开考虑每一位后得出的结论类似于比较两个数的大小关系,
于是我们将一种方案抽象为一个长为\(n\)的二进制数\(now\),
\(0\)表示\(|\),\(1\)表示\(\&\),\(0-0\)表示在\(0\)前插入\(|\)。
那么我们之前推出的结论可以转化为:
考虑\(r_i\)的第\(j\)位和在其之前的\(n\)个\(bit\),
\(0-0\)和\(1-1\)对结果没有意义;
如果这一位上是\(1\),那么在最后一个\(0-1\)之后不存在\(1-0\);
如果这一位上是\(0\),那么在最后一个\(1-0\)之后不存在\(0-1\),或者不存在\(1-0/0-1\)的情况;
可以发现如果将这\(n\)个\(bit\)转化为一个二进制数\(b_j\),
这就是一个严格小于\((now<b_j)\)和大于等于\((now\ge b_j)\)。
那么对于每个询问扣出其边界\(x\le now <y\),那么方案数就是两者之间的二进制数的个数。
对转化后的\(b_j\)排个序就好了。
复杂度为\(O(nmlogn+qm)\),可以通过\(100\%\)的数据点。

#include<bits/stdc++.h>
#define FL "a"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=1e3+10;
const int M=5e3+10;
const int mod=1e9+7;
inline ll read(){ll data=0,w=1;char ch=getchar();while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();if(ch=='-')w=-1,ch=getchar();while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();return data*w;
}
inline void file(){freopen(FL".in","r",stdin);freopen(FL".out","w",stdout);
}
int n,m,q;char s[M];
int pw[N],a[N][M],r[N][M];
int val[M],o[M],p[M];
inline void upd(int &a,int b){a+=b;if(a>=mod)a-=mod;}
inline void dec(int &a,int b){a-=b;if(a<0)a+=mod;}
inline bool cmp(int i,int j){for(int k=n;k;k--)if(a[k][i]!=a[k][j])return a[k][i]<a[k][j];return 0;
}
int main()
{n=read();m=read();q=read();for(int i=pw[0]=1;i<=n+1;i++)pw[i]=2ll*pw[i-1]%mod;for(int i=1;i<=n;i++){scanf("%s",s+1);for(int j=1;j<=m;j++)a[i][j]=s[j]-48;}for(int i=1;i<=q;i++){scanf("%s",s+1);for(int j=1;j<=m;j++)r[i][j]=s[j]-48;}val[0]=0;val[m+1]=pw[n];o[m+1]=m+1;for(int i=1;i<=m;i++)for(int j=n;j;j--)if(a[j][i])upd(val[i],pw[j-1]);for(int i=1;i<=m;i++)o[i]=i;sort(o+1,o+m+1,cmp);for(int i=1;i<=m;i++)p[o[i]]=i;for(int i=1,x,y,res;i<=q;i++){x=0;y=m+1;for(int j=1;j<=m;j++)if(r[i][j])y=min(y,p[j]);else x=max(x,p[j]);if(x>=y){puts("0");continue;}dec(res=val[o[y]],val[o[x]]);printf("%d\n",res);}return 0;
}

D1T2:转盘

一个转盘上有摆成一圈的\(n\)个物品(编号1~\(n\)),其中的\(i\)个物品会在\(t_i\)时刻出现。
在0时刻时,小G可以任选\(n\)个物品中的一个,我们将其编号为\(s_0\)。
并且如果\(i\)时刻选择了物品\(s_i\),那么\(i+1\)时刻可以继续选择当前物品或选择下一个物品(\(s_i\%n+1\))。
在每一时刻(包括\(0\)时刻),如果小G选择的物品已经出现了,那么小G将会标记它。
小H想知道,在物品选择的最优策略下,小G什么时候能标记所有物品?
\(n,m,t_i\le 10^5\),带修+强制在线。

首先我们知道最优方案一定可以只绕一圈。
理由是假设最优方案中最后标记的哪一个物品为\(x\),
那么第一次直接从\(x+1\)开始然后等到其出现显然不会更劣。
直接暴力枚举起点维护答案时间复杂度为\(O(n^2m)\)。

考虑稍作转化,记录一个长度为\(2n\)的数组\(a\),\(a_i=T_i-i,a_{i+n}=a_{i}\),
那么答案为\(min_{i=1}^n\{max_{j=1}^n\{a_{i+j-1}\}+i\}+n-1\)。
经典的滑动窗口问题,使用单调队列维护最大值,时间复杂度降为\(O(nm)\)。

现在考虑如何快速维护这\(n\)个长为\(n\)的窗口。
根据题目性质,\(a_{i+n}=t_i-(i+n)<t_i-i=a_i\),所以只要维护起点为\(1-n\),长度\(\ge n\)的窗口即可。
那么我们可以维护\(min_{i=1}^n\{max_{j=i}^{2n}\{a_{i+j-1}\}+i\}+n-1\)
于是考虑在线段树上维护最大值\(mx[x]\)和\(ans[x]=min_{i=l}^{mid}\{max_{j=i}^r\{a_j\}+i\}\),根据各个节点的情况讨论一下进行修改即可。
最后一个节点表示的区间为\([1,2n]\),则\(ans[rt]\)即为所求。

#include<bits/stdc++.h>
#define FL "a"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=2e5+20;
const int inf=2147483647;
const int mod=998244353;
inline ll read(){ll data=0,w=1;char ch=getchar();while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();if(ch=='-')w=-1,ch=getchar();while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();return data*w;
}
inline void file(){freopen(FL".in","r",stdin);freopen(FL".out","w",stdout);
}
int n,m,p,a[N];
int mx[N<<2],ans[N<<2];
#define ls (i<<1)
#define rs (i<<1|1)
#define mid ((l+r)>>1)
inline int getans(int i,int l,int r,int x){if(l==r)return l+max(mx[i],x);if(mx[rs]>=x)return min(ans[i],getans(rs,mid+1,r,x));return min(getans(ls,l,mid,x),mid+1+x);
}
inline void update(int i,int l,int r){mx[i]=max(mx[ls],mx[rs]);ans[i]=getans(ls,l,mid,mx[rs]);
}
void build(int i,int l,int r){if(l==r){mx[i]=a[l];ans[i]=inf;return;}build(ls,l,mid);build(rs,mid+1,r);update(i,l,r);
}
void insert(int i,int l,int r,int p,int x){if(l==r){mx[i]=x;return;}p<=mid?insert(ls,l,mid,p,x):insert(rs,mid+1,r,p,x);update(i,l,r);
}
int main()
{n=read();m=read();p=read();for(int i=1;i<=n;i++)a[i]=a[i+n]=read();for(int i=1;i<=2*n;i++)a[i]-=i;int res;build(1,1,2*n);printf("%d\n",res=ans[1]+n-1);for(int i=1,x,y;i<=m;i++){x=read();y=read();if(p)x^=res,y^=res;a[x]=y-x;insert(1,1,2*n,x,y-x);a[x+n]=y-x-n;insert(1,1,2*n,x+n,y-x-n);printf("%d\n",res=ans[1]+n-1);}return 0;
}

同时感谢litble的题解教会了我做这道题。

D1T3:毒瘤

求\(n\)个点\(m\)条边的独立集方案数。
\(n\le 10^5,m\le n+10\)

令\(k=m-n+1\),表示这个图比树多出了\(k\)条边。
一个简单的想法是暴力枚举\(k\)条边所对应的\(2k\)个点的情况然后\(O(n)\ dp\),可以获得\(55\)分。

写的时候已经知道要用虚树了,所以我强行把这\(2k\)个点套了一棵虚树上去,
然后我的\(dp\)状态是\(f[i][S]\)表示当前节点子树内对应选择情况的方案数,\(S\)的大小是\(2^{2k}\);
预处理出虚树的每一条边的两个端点在不同选择情况下对应的方案数,预处理的总复杂度是\(O(n)\)。
树形\(dp\)的同时将子树内关键点的选择情况合并,根据树形背包的复杂度,
这个东西的复杂度好象是\(O(2^{2k})\)。
发现不能直接开这么大的数组,所以使用指针根据子树内关键点的大小动态分配内存,空间减小了一半;
然后就卡着时间卡着空间过了这题。(
下面放一放我这题的丑陋代码

#include<bits/stdc++.h>
#define FL "a"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=1e5+20;
const int mod=998244353;
inline ll read(){ll data=0,w=1;char ch=getchar();while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();if(ch=='-')w=-1,ch=getchar();while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();return data*w;
}
inline void file(){freopen(FL".in","r",stdin);freopen(FL".out","w",stdout);
}void print(int x,int d){if(d)print(x>>1,d-1),putchar(48+(x&1));}
inline void upd(int &a,int b){a+=b;if(a>=mod)a-=mod;}
inline void dec(int &a,int b){a-=b;if(a<0)a+=mod;}
inline int poww(int a,int b){int res=1;for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)res=1ll*a*res%mod;return res;
}int n,m,e,rt,ans;
struct edge{int u,v;}E[N];
int F[N];int find(int x){return F[x]?F[x]=find(F[x]):x;}
int head[N],nxt[N<<1],to[N<<1],cnt;
inline void add(int u,int v){to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;}
int headv[N],nxtv[N<<1],tov[N<<1],sum[N<<1][2][2],cnte;
inline void addv(int u,int v){tov[++cnte]=v;nxtv[cnte]=headv[u];headv[u]=cnte;
}int fa[N],dep[N],sz[N],son[N],top[N],w[N],fw[N],cntw;
inline bool cmp_w(int i,int j){return w[i]<w[j];}
void dfs1(int u,int ff){fa[u]=ff;dep[u]=dep[ff]+1;sz[u]=1;son[u]=0;for(int i=head[u],v;i;i=nxt[i]){v=to[i];if(v==ff)continue;dfs1(v,u);sz[u]+=sz[v];if(sz[son[u]]<sz[v])son[u]=v;}
}
void dfs2(int u,int tp){top[u]=tp;w[u]=++cntw;fw[cntw]=u;if(son[u])dfs2(son[u],tp);for(int i=head[u],v;i;i=nxt[i]){v=to[i];if(v==fa[u]||v==son[u])continue;dfs2(v,v);}
}
inline int lca(int u,int v){while(top[u]!=top[v])dep[top[u]]>dep[top[v]]?u=fa[top[u]]:v=fa[top[v]];return dep[u]<dep[v]?u:v;
}int t[N],id[N],k,p,cntk,cal[N],cov,col[N];
int f[N][2],vis[N];
void dfs3(int u,int ff){f[u][0]=f[u][1]=1;vis[u]=1;if(id[u]!=-1){f[u][col[u]^1]=0;return;}for(int i=head[u],v;i;i=nxt[i]){v=to[i];if(v==ff)continue;dfs3(v,u);f[u][0]=1ll*(f[v][0]+f[v][1])%mod*f[u][0]%mod;f[u][1]=1ll*f[v][0]*f[u][1]%mod;}
}
int sub[N],lw[N];
int low[1<<22],dp[33554432],len,*g[N][2],siz[N];
void dfs4(int u,int ff){static int tmp[1<<22];vis[u]=1;if(id[u]<k)id[u]=cntk++,siz[u]=1,lw[u]=1<<id[u];for(int i=headv[u],v;i;i=nxtv[i]){v=tov[i];if(v==ff)continue;dfs4(v,u);siz[u]+=siz[v];lw[u]|=sub[v];for(int c=0;c<2;c++){col[u]=c;dfs3(fa[v],v);sum[i][c][0]=(f[fa[v]][0]+f[fa[v]][1])%mod;sum[i][c][1]=f[fa[v]][0];}}lw[u]=low[lw[u]];g[u][0]=dp+len;len+=1<<siz[u]+1;g[u][1]=dp+len;len+=1<<siz[u]+1;g[u][0][0]=g[u][1][0]=1;for(int i=head[u],v;i;i=nxt[i]){v=to[i];if(v==fa[u]||vis[v])continue;dfs3(v,u);g[u][0][0]=1ll*(f[v][0]+f[v][1])*g[u][0][0]%mod;g[u][1][0]=1ll*f[v][0]*g[u][1][0]%mod;}for(int i=headv[u],v;i;i=nxtv[i]){v=tov[i];if(v==ff)continue;for(int c=0;c<2;c++){for(int all=(sub[u]|sub[v])>>lw[u],s=all;;s=(s-1)&all){tmp[s]=0;if(!s)break;}for(int d=0;d<2;d++)for(int s=sub[u];;s=(s-1)&sub[u]){for(int t=sub[v];;t=(t-1)&sub[v]){if(g[u][c][s>>lw[u]]&&g[v][d][t>>lw[v]]&&sum[i][c][d])upd(tmp[(s|t)>>lw[u]],1ll*g[u][c][s>>lw[u]]*g[v][d][t>>lw[v]]%mod*sum[i][c][d]%mod);if(!t)break;}if(!s)break;}for(int all=(sub[u]|sub[v])>>lw[u],s=all;;s=(s-1)&all){g[u][c][s]=tmp[s];if(!s)break;}}sub[u]|=sub[v];}if(id[u]<k){for(int all=(sub[u]|1<<id[u])>>lw[u],s=all;;s=(s-1)&all){tmp[s]=0;if(!s)break;}for(int s=sub[u];;s=(s-1)&sub[u]){upd(tmp[(s|1<<id[u])>>lw[u]],g[u][1][s>>lw[u]]);if(!s)break;}for(int all=(sub[u]|1<<id[u])>>lw[u],s=all;;s=(s-1)&all){g[u][1][s]=tmp[s];if(!s)break;}sub[u]|=1<<id[u];}
}int main()
{n=read();m=read();memset(id,-1,sizeof(id));for(int i=1,u,v,fu,fv;i<=m;i++){u=read();v=read();fu=find(u);fv=find(v);if(fu!=fv)F[fu]=fv,add(u,v),add(v,u);else{if(id[u]==-1){t[k]=u;id[u]=k;k++;}if(id[v]==-1){t[k]=v;id[v]=k;k++;}E[++e]=(edge){u,v};}}if(!k)return dfs3(1,0),printf("%d\n",ans=(f[1][0]+f[1][1])%mod),0;for(rt=1;id[rt]==-1;rt++);dfs1(rt,0);dfs2(rt,0);sort(t,t+k,cmp_w);p=k;for(int i=1;i<k;i++){t[p]=lca(t[i],t[i-1]);if(id[t[p]]==-1)id[t[p]]=p,p++;}for(int s=1;s<(1<<k);s++)low[s]=s&1?0:low[s>>1]+1;sort(t,t+p);p=unique(t,t+p)-t;sort(t,t+p,cmp_w);for(int i=0;i<p;i++){while(cov&&w[cal[cov]]+sz[cal[cov]]-1<w[t[i]])cov--;if(cov)addv(cal[cov],t[i]),addv(t[i],cal[cov]);cal[++cov]=t[i];}dfs4(rt,0);for(int c=0;c<2;c++)for(int s=sub[rt];;s=(s-1)&sub[rt]){bool pd=1;for(int i=1;i<=e;i++)if((s&1<<id[E[i].u])&&(s&1<<id[E[i].v])){pd=0;break;}if(pd)upd(ans,g[rt][c][s>>lw[rt]]);if(!s)break;}printf("%d\n",ans);return 0;
}

上面的愚蠢做法实际上忽略了一个重要的优化:只需要枚举多出的边中每一对点的选择情况。
就算直接枚举\(0-0,0-1,1-0\)三种情况也比上面的方法要好。
实际只须枚举两种情况:某个节点不选,另一个节点随意/这个节点要选,对应的节点强制不选。
然后直接\(dp\)就能有\(75\)分,再加上虚树即可无压力\(O(n+k2^k)\ AC\)。

D2T1:游戏

感觉是个神仙题啊,不知道为什么你们都把它当sb题切

看到\(y\le x\)的部分分感觉可以线段树上二分暴力搞搞,于是觉得正解也可以这样做
于是就陷入了无穷无尽的调试中...

我们考虑比暴力更加优秀一些的方法:记忆化搜索。然后就过了此题
首先我们把没有上锁的房间连成一块。
到了一个新的房间时,我们要保证它是已经被搜索过了的。
一个很简单的想法是,对于一扇上锁的门,如果钥匙在它左边,那么我们肯定先求解位于门右边的房间的答案,如果从左边可以到达右边,那么直接加上右边搜索的结果即可。

那么我们对于一扇上锁的门,从钥匙所在方向的反方向的房间向钥匙所在的方向的房间连边,代表先求解反方向;
然后按照拓扑序依次进行求解即可。

#include<bits/stdc++.h>
#define FL "game"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=1e6+20;
const int inf=2147483647;
const int mod=998244353;
inline ll read(){ll data=0,w=1;char ch=getchar();while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();if(ch=='-')w=-1,ch=getchar();while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();return data*w;
}
inline void file(){freopen(FL".in","r",stdin);freopen(FL".out","w",stdout);
}int n,m,k,q,key[N],id[N];
int d[N],head[N],nxt[N<<1],to[N<<1],cnt;
inline void add(int u,int v){d[v]++;to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}
queue<int>Q;int L[N],R[N];inline void solve(int i){bool sl=0,sr=0;while(!sl||!sr){sl=sr=0;if(L[i]==1)sl=1;else{if(L[i]<=key[L[i]-1]&&key[L[i]-1]<=R[i]){L[i]=L[L[i]-1];sl=sr=0;}else sl=1;}if(R[i]==k)sr=1;else{if(L[i]<=key[R[i]]&&key[R[i]]<=R[i]){R[i]=R[R[i]+1];sl=sr=0;}else sr=1;}}
}int main()
{n=read();m=read();q=read();for(int i=1,x,y;i<=m;i++){x=read();y=read();key[x]=y;}for(int i=k=1;i<=n;i++){id[i]=k;if(key[i])k++;}for(int i=1;i<n;i++)if(key[i])key[id[i]]=id[key[i]];for(int i=1;i<=k;i++)L[i]=R[i]=i;for(int i=1;i<k;i++)key[i]<=i?add(i+1,i):add(i,i+1);for(int i=1;i<=k;i++)if(!d[i])Q.push(i);while(!Q.empty()){int u=Q.front();Q.pop();solve(u);for(int i=head[u],v;i;i=nxt[i]){d[v=to[i]]--;if(!d[v])Q.push(v);}}for(int i=1,s,t;i<=q;i++){s=read();t=read();s=id[s];t=id[t];if(t<L[s]||R[s]<t)puts("NO");else puts("YES");}return 0;
}

D2T2:排列

给定\(n\)个整数\(a_1,a_2,\dots,a_n,0\le ai\le n\),以及\(n\)个整数\(w_1,w_2,\dots,w_n\)。
称 \(a_1, a_2, \dots, a_n\)的 一个排列 \(a_{p[1]}, a_{p[2]}, \dots, a{p[n]}\)为 \(a_1, a_2, \dots, a_n\)的一个合法排列,
当且仅当该排列满足:
对于任意 的 \(k\) 和任意的 \(j\),如果 \(j \le k\),那么 \(a_{p[j]}\)不等于 \(p[k]\)。
(换句话说就是:对于任意的 \(k\) 和任意的 \(j\),如果 \(p[k]\)等于 \(ap[j]\),那么\(k<j\)。)
定义这个合法排列的权值为 \(w_{p[1]} + 2w_{p[2]} + \dots + nw_{p[n]}\)。
你需要求出在所有合法排列中的最大权值。如果不存在合法排列,输出\(-1\)。
\(n\le 5\times 10^5\)

考虑转化题意,\(a_i=k\)表示重新排列后\(a_k\)要在\(a_i\)前面,那么连一条\(k->i\)的有向边。
可以发现这样转化之后,只要能在图中选择一个合法的拓扑序,就能形成一个合法排列。
于是图中有环即无解,无环后每个点仅有\(1\)入度,形成了一棵以\(0\)为根的外向树。
问题转化为:给出一棵有点权的树,从根节点出发选择一个树的遍历顺序,第\(i\)个点经过时间为\(t\)时会给答案加上\(tw_i\)的贡献,求最大总贡献。
你可以发现树上序列\(dp\)归并是正确的,时间复杂度为\(O(n^2)\),可以得到\(60\)分。

我们知道如果一个点权值非常小,那么选择父亲后肯定优先选择它;
于是考虑贪心,每次选择一个权值最小的点,将其缩到父亲上并贡献答案,
父亲的权值变成所在节点的平均值。
具体细节可以看代码 or 别的题解...
这份\(set\)的代码不开\(O2\)是过不去的...

#include<bits/stdc++.h>
#define FL "a"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=5e5+20;
const int inf=2147483647;
const int mod=998244353;
const ll INF=1ll<<60;
inline ll read(){ll data=0,w=1;char ch=getchar();while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();if(ch=='-')w=-1,ch=getchar();while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();return data*w;
}
inline void file(){freopen(FL".in","r",stdin);freopen(FL".out","w",stdout);
}int n,a[N];
int f[N];int find(int x){return f[x]!=-1?f[x]=find(f[x]):x;}
int head[N],nxt[N<<1],to[N<<1],cnt;
inline void add(int u,int v){to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}int fa[N],sz[N];ll w[N],ans;
struct node{ll val;int id;};
inline bool operator <(node a,node b){if(a.val*sz[b.id]!=b.val*sz[a.id])return a.val*sz[b.id]<b.val*sz[a.id];else return a.id<b.id;
}
set<node>S;set<node>::iterator it1,it2;
int main()
{n=read();memset(f,-1,sizeof(f));for(int i=1;i<=n;i++)a[i]=read();for(int i=1;i<=n;i++)w[i]=read();for(int i=1;i<=n;i++){if(find(i)==find(a[i]))return puts("-1"),0;f[find(i)]=find(a[i]);add(a[i],i);fa[i]=a[i];}w[0]=INF;for(int i=0;i<=n;i++){sz[i]=1;f[i]=-1;S.insert((node){w[i],i});}while(S.size()!=1){it1=S.begin();int u=it1->id,ff=find(fa[u]);it2=S.find((node){w[ff],ff});S.erase(it1);S.erase(it2);ans+=w[u]*sz[ff];if(ff)w[ff]+=w[u];sz[ff]+=sz[u];S.insert((node){w[ff],ff});f[find(u)]=ff;}printf("%lld\n",ans);return 0;
}

D2T3:道路

dp状态为\(f[i][a][b]\),没什么好说的。
从前的码风...

#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cstring>
#include<complex>
#include<vector>
#include<cstdio>
#include<string>
#include<bitset>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define FILE "a"
#define mp make_pair
#define pb push_back
#define RG register
#define il inline
using namespace std;
typedef unsigned long long ull;
typedef vector<int>VI;
typedef long long ll;
typedef double dd;
const dd eps=1e-10;
const int mod=1e9+7;
const int N=40010;
const dd pi=acos(-1);
il ll read(){RG ll data=0,w=1;RG char ch=getchar();while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();if(ch=='-')w=-1,ch=getchar();while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();return data*w;
}
il void file(){freopen(FILE".in","r",stdin);freopen(FILE".out","w",stdout);
}
ll f[105][52][52],s[2][N],a[N],b[N],c[N],n,dfn[N];
il void dfs(int i,int now){dfn[i]=now;if(s[0][i])dfs(s[0][i],now+1);if(s[1][i])dfs(s[1][i],now+2);if(s[0][i]&&s[1][i])for(RG int j=0;j<=40;j++)for(RG int k=0;j+k<=40;k++)f[dfn[i]][j][k]=min(f[dfn[s[0][i]]][j+1][k]+f[dfn[s[1][i]]][j][k],f[dfn[s[0][i]]][j][k]+f[dfn[s[1][i]]][j][k+1]);elsefor(RG int j=0;j<=40;j++)for(RG int k=0;j+k<=40;k++)f[dfn[i]][j][k]=1ll*c[i]*(a[i]+j)*(b[i]+k);
}
int main()
{n=read();for(RG int i=1;i<n;i++){s[0][i]=read();if(s[0][i]<0)s[0][i]=-s[0][i]+n-1;s[1][i]=read();if(s[1][i]<0)s[1][i]=-s[1][i]+n-1;}for(RG int i=n;i<=2*n-1;i++)a[i]=read(),b[i]=read(),c[i]=read();dfs(1,1);printf("%lld\n",f[dfn[1]][0][0]);return 0;
}

转载于:https://www.cnblogs.com/cjfdf/p/10444212.html

HNOI/AHOI2018题解相关推荐

  1. BZOJ5286:[HNOI/AHOI2018]转盘——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=5286 https://www.luogu.org/problemnew/show/P4425 ht ...

  2. BZOJ5289 洛谷4437:[HNOI/AHOI2018]排列——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=5289 https://www.luogu.org/problemnew/show/P4437 考虑 ...

  3. BZOJ5290 洛谷4438:[HNOI/AHOI2018]道路——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=5290 https://www.luogu.org/problemnew/show/P4438 的确 ...

  4. 洛谷4438 [HNOI/AHOI2018]道路

    标签:树形DP 题目 题目传送门 题目描述 W 国的交通呈一棵树的形状.W 国一共有n−1n - 1n−1个城市和nnn个乡村,其中城市从111到n−1n - 1n−1 编号,乡村从111到nnn编号 ...

  5. Luogu 4438 [HNOI/AHOI2018]道路

    $dp$. 这道题最关键的是这句话: 跳出思维局限大胆设状态,设$f_{x, i, j}$表示从$x$到根要经过$i$条公路,$j$条铁路的代价,那么对于一个叶子结点,有$f_{x, i, j} = ...

  6. BZOJ5286 HNOI/AHOI2018转盘(分块/线段树)

    显然最优走法是先一直停在初始位置然后一次性走完一圈.将序列倍长后,相当于找一个长度为n的区间[l,l+n),使其中ti+l+n-1-i的最大值最小.容易发现ti-i>ti+n-(i+n),所以也 ...

  7. luogu P4438 [HNOI/AHOI2018]道路

    题目传送门:https://www.luogu.org/problemnew/show/P4438 题意: 有n-1个点为城市,n-1个点为农村,每个城市连出两种道路,一边为公路,一边为铁路.现在每一 ...

  8. P4438 [HNOI/AHOI2018]道路

    这题倒是在树形dp的基础上添加了一些操作,但是做好预处理之后跟模板就差不多了 #include<cstdio> #include<cstring> #define neko 1 ...

  9. 阿里巴巴编程工作导师待遇_导师制iOS应用中的泛型编程之旅

    阿里巴巴编程工作导师待遇 嗨,大家好, (Hi everyone,) In this post, I give a tour of how we're using Generic programmin ...

最新文章

  1. (C++)A+B 输入输出练习V 输入的第一行是一个正数N,表示后面有N行。每一行的第一个数是M,表示本行后面还有M个数。
  2. Linux下CMake简明教程(九) 添加控制选项
  3. php7安装kafka扩展(已经测试)
  4. 电脑卡顿不流畅是什么原因_什么造成游戏直播画画卡顿、延迟?这三个原因了解一下...
  5. 【LeetCode】剑指 Offer 15. 二进制中1的个数
  6. 转 JMeter之修改Sampler响应数据的编码格式
  7. Python自动化中的元素定位xpath(二)
  8. vs2017结合qt开发,vs报错找不到库(解决方案)
  9. 关于RDP报表工具参数配置
  10. 开源H5棋牌 cocos creator微信棋牌小游戏 幼麟棋牌服务端分析笔记
  11. linux开启vt虚拟化,VT虚拟化如何开启
  12. 流氓软件自动安装恶意插件导致浏览器闪退问题
  13. 2018年9月份面试小记
  14. 54. Java序列化三连问,是什么?为什么需要?如何实现?
  15. 0011基于单片机电子密码锁控制系统设计
  16. 瑞吉外卖:linux课程学习(软件安装、项目部署)
  17. PTA基础编程题目集7-33 有理数加法
  18. 个人英雄主义和个人品牌
  19. android教程 kotlin,Kotlin for Android使用教程(一)
  20. 用Java分割和合并PDF文件

热门文章

  1. 这所院校23年录取610人,政英单科线38分,初试过线47分全部录取!
  2. 实现单链表的各种基本运算的算法
  3. 使用SpringCloud实战微服务
  4. 关于ModifyStyle ModifyStyleEx修改自定义控件的问题
  5. 华为HI第二款车,阿维塔11的智能化有什么特点?
  6. 4G价格战一触即发:流量包月还是愿望
  7. 弹性理论法研究桩基受力计算公式_水平荷载作用下群桩计算方法研究
  8. 云容灾是什么意思?云容灾和传统容灾的区别?
  9. 为什么要学习JDBC?
  10. 【转载】html网页字体颜色代码大全