做爲一名高二老年選手來補一下我省去年的省選題。node
按順序給出\(n\)個\(m\)位的二進制數\(a_i\),再在最前方添一個\(0\),
給出\(q\)次詢問,每次詢問給出一個一樣長爲\(m\)的二進制數\(r_i\),
要求在以前給出的\(n+1\)個二進制數的每相鄰兩個數的空位添加按位與運算符或按位或運算符,
一共\(n\)個,並使得這個算式獲得的值爲\(r_i\),求方案數。
\(n,q\le 1000,m\le 5000\)ios
暴力\(30\%\)不提。c++
對每一位分開考慮。
根據 [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就過了ui
#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\%\)的數據點。spa
#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; }
一個轉盤上有擺成一圈的\(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\),帶修+強制在線。.net
首先咱們知道最優方案必定能夠只繞一圈。
理由是假設最優方案中最後標記的哪個物品爲\(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的題解教會了我作這道題。
求\(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\)。
感受是個神仙題啊,不知道爲何大家都把它當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; }
給定\(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; }
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]); else for(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; }