收錄了最近本人完成的一部分codeforces習題,不按期更新node
因此實際上是補題記錄QAQios
注意到若是隻使用某一種物品,那麼這八種物品能夠達到的最小相同重量爲$840$c++
故答案必定能夠被寫成$840k+x(k,x\in N_+)$,咱們將$x$稱爲」餘下的部分」數組
故而設$dp[i][j]$爲當前考慮了前$i$個物品,它們所佔的餘下的部分的重量爲$j$時,最多能夠組成多少個$840$數據結構
對於每一個$i$預處理出枚舉上界暴力轉移便可oop
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) #define maxd 1000000007 typedef long long ll; const int N=100000; const double pi=acos(-1.0); ll w,a[10],ans=0,dp[9][100100]; ll read() { ll x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } int main() { w=read(); rep(i,1,8) a[i]=read(); ll maxp=8*840; memset(dp,-1,sizeof(dp)); dp[0][0]=0; rep(i,1,8) { rep(j,0,maxp)//餘下的重量 { if (dp[i-1][j]==-1) continue; ll k=min(1ll*840/i,a[i]); rep(p,0,k)//當前有多少做爲餘下的部分 { dp[i][j+p*i]=max(dp[i][j+p*i],dp[i-1][j]+(a[i]-p)/(840/i)); } } } ll ans=0; rep(i,0,min(w,maxp)) { if (dp[8][i]==-1) continue; ans=max(ans,i+min((w-i)/840,dp[8][i])*840); } printf("%lld",ans); return 0; }
根據$lcm(x,y)=\frac{xy}{gcd(x,y)}$進行計算ui
枚舉約數$d$,每次找到知足$d|x$的最小的兩個$x$,用它們更新答案便可spa
爲何這樣作可行?咱們假設知足$d|x$的數從小到大一次爲$a_1,a_2,\cdots,a_k$code
不妨對$a_1,a_2,a_k(k>2)$這三個數進行分析,而且咱們保證$gcd(a_1,a_k)=d$,不然咱們能夠在枚舉更大的$d$的時候考慮到這一組orm
證畢
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) typedef long long ll; const int N=100000; const double pi=acos(-1.0); int n,a[1001000],cnt[10010000]; vector<int> ans; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } ll gcd(ll x,ll y) { if (!y) return x;else return gcd(y,x%y); } int main() { n=read();ll maxd=0; rep(i,1,n) {a[i]=read();cnt[a[i]]++;maxd=max(maxd,1ll*a[i]);} ll lcm=(ll)1e18+7,val1,val2; rep(i,1,maxd) { int j;ans.clear(); for (j=i;j<=maxd;j+=i) { if (!cnt[j]) continue; int tmp=cnt[j]; while ((tmp) && (ans.size()<2)) { ans.push_back(j); tmp--; } if (ans.size()==2) break; } if (ans.size()!=2) continue; ll now=1ll*ans[0]*ans[1]/gcd(ans[0],ans[1]); if (now<lcm) { lcm=now;val1=ans[0];val2=ans[1]; } } ll pos1=0,pos2=0; rep(i,1,n) { if ((!pos1) && (a[i]==val1)) pos1=i; else if ((!pos2) && (a[i]==val2)) pos2=i; } if (pos1>pos2) swap(pos1,pos2); printf("%lld %lld",pos1,pos2); return 0; }
若是不要輸出方案的話那就能夠大力$dp$,記$dp[u][0/1]$爲控制以$u$爲根的子樹的最小代價,其中$0$表示不選$u$的祖先$1$表示選,考慮$u$的兒子是不須要選祖先或者某一個須要祖先來進行轉移
然而彷佛輸出方案很難寫。。。棄了棄了看題解
將控制一個點的操做轉化到樹的dfs序上,也就是控制了一段區間,注意到這個$dfs$序咱們只須要保留葉子結點
爲了轉化區間操做,咱們將這個$dfs$轉化成差分序列,即一次對$[l,r]$的操做能夠看作是在$l$加上一個數同時在$r+1$上減去一個數
考慮題目是要求最後能使得整個序列都變成$0$,等價於讓這個差分序列變成$0$
也就是說對於每一個點咱們都但願有一條能單獨修改它的路徑
將差分序列的每一個位置當作是一個點,一次修改當作是一條邊,跑kruskal便可
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) #define maxd 1000000007 typedef long long ll; const int N=100000; const double pi=acos(-1.0); struct sqnode{ int to,nxt; }sq[400400]; struct edgenode{ int u,v,w,id; }edge[200200]; bool operator <(const edgenode &p,const edgenode &q) { return p.w<q.w; } int n,w[200200],all=0,head[200200],l[200200],r[200200],tot=0,tim=0,fa[200200]; bool vis[200200]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } int find(int x) { if (fa[x]==x) return x; fa[x]=find(fa[x]); return fa[x]; } void dfs(int u,int fu) { int i; l[u]=maxd;r[u]=0; for (i=head[u];i;i=sq[i].nxt) { int v=sq[i].to; if (v==fu) continue; dfs(v,u);vis[u]=1; l[u]=min(l[u],l[v]); r[u]=max(r[u],r[v]); } if ((!vis[u]) && (u!=1)) {l[u]=(++tim);r[u]=tim;} edge[++tot]=(edgenode){l[u],r[u]+1,w[u],u}; } void add(int u,int v) { all++;sq[all].to=v;sq[all].nxt=head[u];head[u]=all; } int main() { n=read(); rep(i,1,n) w[i]=read(); rep(i,1,n-1) { int u=read(),v=read(); add(u,v);add(v,u); } memset(vis,0,sizeof(vis)); dfs(1,0); sort(edge+1,edge+1+n); ll ans=0; rep(i,1,tim+1) fa[i]=i; memset(vis,0,sizeof(vis)); int l=1,r=1; for (l=1;l<=n;l=r+1) { while ((r<n) && (edge[r+1].w==edge[l].w)) r++; rep(j,l,r) { int x=edge[j].u,y=edge[j].v, fx=find(x),fy=find(y); if (fx!=fy) vis[edge[j].id]=1; } rep(j,l,r) { int x=edge[j].u,y=edge[j].v, fx=find(x),fy=find(y); if (fx!=fy) {fa[fx]=fy;ans+=edge[j].w;} } } int cnt=0; rep(i,1,n) if (vis[i]) cnt++; printf("%lld %d\n",ans,cnt); rep(i,1,n) if (vis[i]) printf("%d ",i); return 0; }
學不來告辭
利用floyd和pollard-rho中的判圈方式,咱們讓$0$號棋子一次走一步,$1$號棋子兩次走一步,直到二者相遇
咱們假設此時$1$號棋子走了$T+x$步,那麼$0$號棋子走了$2(T+x)$步
且應有$T+x\equiv 0(mod\ C)$
所以$0$和$1$號這兩顆棋子再走$T$步便可到達終點
且剩下的$8$棵棋子須要走一條鏈的長度,也是$T$步
因而咱們能夠在$2x+3T$的步數內完成這一過程,因爲$x<C$,因此總步數小於$3(T+C)$
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) #define maxd 1000000007 typedef long long ll; const int N=100000; const double pi=acos(-1.0); char s[20]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } int get_num() { int ans=read(); rep(i,1,ans) scanf("%s",s); return ans; } int main() { while (1) { printf("next 0\n");fflush(stdout); int cnt=get_num(); printf("next 0 1\n");fflush(stdout); cnt=get_num(); if (cnt==2) break; } while (1) { printf("next 0 1 2 3 4 5 6 7 8 9\n");fflush(stdout); int cnt=get_num(); if (cnt==1) break; } printf("done\n");fflush(stdout); return 0; }
數據範圍暗示矩乘系列
考慮最原始的$dp$,假設序列裏有$z$個$0$,$n-z$個$1$,目標狀態是前$z$個均是$0$
記$dp[i][j]$爲前$i$位有$j$個$1$時的方案數,顯然答案是$dp[z][0]$除上總方案數
在轉移的時候$dp[i][j]$可能轉移到$j-1,j.j+1$,具體的有
轉移到$j$時
轉移到$j-1$時
轉移到$j+1$時
因爲$dp[i]$所有至於$dp[i-1]$有關,咱們維護$dp[i]$的矩陣便可
貌似將轉移矩陣右乘可使得它和普通的轉移區別不大?
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) #define maxd 1000000007 typedef long long ll; const double pi=acos(-1.0); int N,n,k,x[120],zero=0; struct matrix{ ll x[120][120]; }ans,a; matrix mul(matrix a,matrix b) { matrix c; rep(i,0,N) rep(j,0,N) c.x[i][j]=0; rep(i,0,N) { rep(j,0,N) { rep(k,0,N) { c.x[i][j]=(c.x[i][j]+a.x[i][k]*b.x[k][j])%maxd; } } } return c; } ll qpow(ll x,ll y) { ll ans=1; while (y) { if (y&1) ans=(ans*x)%maxd; x=(x*x)%maxd; y>>=1; } return ans; } matrix qpow(matrix a,int y) { matrix ans; rep(i,0,N) ans.x[i][i]=1; while (y) { if (y&1) ans=mul(ans,a); a=mul(a,a); y>>=1; } return ans; } ll inv(ll x) {return qpow(x,maxd-2);} int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } ll C(ll x,ll y) { return (1ll*x*(x-1)/2)%maxd; } int main() { n=read();k=read(); rep(i,1,n) {x[i]=read();zero+=(x[i]==0);} int now=0; rep(i,1,zero) now+=(x[i]==1); N=min(n-zero,zero); rep(i,0,N) { a.x[i][i]=(a.x[i][i]+C(zero,2)+C(n-zero,2))%maxd; a.x[i][i]=(a.x[i][i]+1ll*i*(n-zero-i))%maxd; a.x[i][i]=(a.x[i][i]+1ll*(zero-i)*i)%maxd; if (i) a.x[i][i-1]=(a.x[i][i-1]+1ll*i*i)%maxd; if (i<N) a.x[i][i+1]=(a.x[i][i+1]+1ll*(zero-i)*(n-zero-i))%maxd; } a=qpow(a,k);ans.x[0][now]=1; ans=mul(ans,a); ll final=ans.x[0][0]; final=(final*inv(qpow(C(n,2),k)))%maxd; printf("%lld",final); return 0; }
考慮$f(l,r)$的實際意義,能夠被看作是遍歷一遍這個區間找到最大值後分治成兩個子問題,求最後的遍歷元素數
考慮位置$i$上的元素,即左邊第一個比它大的數的位置是$l_i$,右邊第一個比它大的數是$r_i$
那麼$i$只會在$[l_i+1,r_i-1]$這個區間纔會被刪去,結合原來區間$[L,R]$知答案就是全部位置的區間長度之和,即$\sum_{i=l}^rmin(R.r_i-1)-max(L,l_i+1)+1$
最簡單的思路就是使用線段樹維護這個答案,區間加$[l_i+1,r_i-1]$便可,可是會出現$L$左邊的點所在的區間對答案產生影響的狀況
此時咱們將原來的區間拆成$[l_i+1,i]$和$[i+1,r_i-1]$兩個區間,這樣的話把原來的雙向區間變成了單向,離線維護便可
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) #define maxd 1000000007 typedef long long ll; const int N=100000; const double pi=acos(-1.0); int n,q,ql[1001000],qr[1001000],l[1001000],r[1001000],a[1001000]; ll seg[8008000],tag[8008000],ans[1001000]; vector<pair<int,int> > lq[1001000],rq[1001000]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } void build(int id,int l,int r) { seg[id]=0;tag[id]=0; if (l==r) return; int mid=(l+r)>>1; build(id<<1,l,mid); build(id<<1|1,mid+1,r); } void pushdown(int id,int l,int r) { if (tag[id]) { tag[id<<1]+=tag[id]; tag[id<<1|1]+=tag[id]; int mid=(l+r)>>1; seg[id<<1]+=tag[id]*(mid-l+1); seg[id<<1|1]+=tag[id]*(r-mid); tag[id]=0; } } void modify(int id,int l,int r,int nowl,int nowr) { pushdown(id,l,r); if ((l>=nowl) && (r<=nowr)) { seg[id]+=(r-l+1);tag[id]++; return; } int mid=(l+r)>>1; if (nowl<=mid) modify(id<<1,l,mid,nowl,nowr); if (nowr>=mid+1) modify(id<<1|1,mid+1,r,nowl,nowr); seg[id]=seg[id<<1]+seg[id<<1|1]; } ll query(int id,int l,int r,int nowl,int nowr) { pushdown(id,l,r); if ((l>=nowl) && (r<=nowr)) return seg[id]; int mid=(l+r)>>1;ll ans=0; if (nowl<=mid) ans+=query(id<<1,l,mid,nowl,nowr); if (nowr>=mid+1) ans+=query(id<<1|1,mid+1,r,nowl,nowr); return ans; } int main() { n=read();q=read(); rep(i,1,n) a[i]=read(); a[0]=maxd;a[n+1]=maxd; rep(i,1,n) { l[i]=i-1; while (a[l[i]]<=a[i]) l[i]=l[l[i]]; } per(i,n,1) { r[i]=i+1; while (a[r[i]]<=a[i]) r[i]=r[r[i]]; } rep(i,1,q) ql[i]=read(); rep(i,1,q) qr[i]=read(); rep(i,1,q) { lq[qr[i]].push_back(make_pair(ql[i],i)); rq[ql[i]].push_back(make_pair(qr[i],i)); } build(1,1,n); rep(i,1,n) { modify(1,1,n,l[i]+1,i); int len=lq[i].size(); rep(j,0,len-1) ans[lq[i][j].second]=query(1,1,n,lq[i][j].first,i); } build(1,1,n); per(i,n,1) { if (i+1<r[i]) modify(1,1,n,i+1,r[i]-1); int len=rq[i].size(); rep(j,0,len-1) ans[rq[i][j].second]+=query(1,1,n,i+1,rq[i][j].first); } rep(i,1,q) printf("%lld ",ans[i]); return 0; }
考慮拆點,將一個博物館按照一個星期的天數拆成$d$個點,一條邊$u->v$對應着鏈接$(u,j)$和$(v,j+1)$($j$表示是這個星期的的第幾天)
而後按照正常劇本咱們應該開始$tarjan$縮點而後在$DAG$上跑$dp$,不過先停一下,想一下這個問題:縮完點以後會不會重複計數呢?即在聯通塊$x$中有一個合法點$(u,i)$,會不會在$x$能夠到達的某個聯通塊$y$中存在另外一個合法點$(u,j)$呢?
咱們設$(u,i)$能到達$(u,j)$,實際意義就是在點$u$能夠通過$i-j$天的時間走一個環,記這個差爲$d$,那麼$(u,j)$通過$d$天以後應該也能走早$(u,j+d)$,一直迭代下去必定會回到$(u,i)$
綜上所述,對於從同一個點拆出來的點,若是其中一個能到達另外一個,那麼另外一個也能走回來,即它們屬於同一個$scc$中
故暴力$dp$便可,利用$tarjan$是拓撲序的倒序能夠省一個$toposort$而直接$dp$
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<stack> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) #define maxd 1000000007 typedef long long ll; const int N=5001000; const double pi=acos(-1.0); struct node{ int to,nxt; }sq1[N],sq2[N]; int n,m,d,head1[N],head2[N],all1=0,all2=0,tot=0,tim=0,dfn[N],low[N], dp[N],cnt[N],col[N],vis[N]; char s[100100][60]; bool in[N]; stack<int> sta; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } int id(int x,int y) { return x+(y-1)*n; } void add1(int u,int v) { all1++;sq1[all1].to=v;sq1[all1].nxt=head1[u];head1[u]=all1; } void add2(int u,int v) { all2++;sq2[all2].to=v;sq2[all2].nxt=head2[u];head2[u]=all2; } void tarjan(int u) { dfn[u]=low[u]=(++tim); sta.push(u);in[u]=1; int i; for (i=head1[u];i;i=sq1[i].nxt) { int v=sq1[i].to; if (!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if (in[v]) low[u]=min(low[u],dfn[v]); } if (dfn[u]==low[u]) { tot++; while (1) { int v=sta.top();sta.pop(); col[v]=tot;in[v]=0; if (u==v) break; } } } int main() { n=read();m=read();d=read(); rep(i,1,m) { int u=read(),v=read(); rep(j,1,d) add1(id(u,j),id(v,j%d+1)); } rep(i,1,n*d) if (!dfn[i]) tarjan(i); rep(i,1,n) scanf("%s",s[i]+1); rep(i,1,n) { rep(j,1,d) { int tmp=id(i,j); if ((s[i][j]=='1') && (vis[col[tmp]]<i)) { vis[col[tmp]]=i;cnt[col[tmp]]++; } } } rep(i,1,n*d) { int j; for (j=head1[i];j;j=sq1[j].nxt) { int v=sq1[j].to; if (col[i]!=col[v]) add2(col[i],col[v]); } } rep(i,1,tot) { int j; for (j=head2[i];j;j=sq2[j].nxt) { int v=sq2[j].to; dp[i]=max(dp[i],dp[v]); } dp[i]+=cnt[i]; } printf("%d",dp[col[id(1,1)]]); return 0; }
首先這個$q\leq 10^5$就是假的,本質不一樣的詢問個數一共有$26*26$種考慮將其所有處理出來
首先咱們將排列轉成組合,對於每一種集合的選定方式,它對應着的排列由可重複的排列公式知爲$\frac{(\frac{n}{2})^2}{\sum cnt_i!}$($cnt_i$表示當前集合中的元素個數)
那麼轉化爲從給定元素中選出$\frac{n}{2}$個元素,知足$x$和$y$同時存在或不存在且全部同種元素要麼全都存在要麼全都不存在
先忽略第一個條件,就是一個01揹包
再考慮有第一個條件,咱們能夠看作是沒有這兩種物品,而後選出$\frac{n}{2}$個元素,而後將其$*2$(做前一半仍是後一半)便可
取消這兩種物品的方案數可使用退揹包完成,具體的記原來的方案爲$f_i$,那麼沒有種類$x$的方案數$g_i$能夠看作沒法從$f_{i-cnt_x}$轉移到$f_i$,故$g_i=f_{i-cnt_x}$
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) #define maxd 1000000007 typedef long long ll; const int N=100000; const double pi=acos(-1.0); int n,cnt[100100],q; ll dp[100100],fac[100100],invfac[100100],same[100][100],tmp[100100]; char s[100100]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } ll qpow(ll x,ll y) { ll ans=1; while (y) { if (y&1) ans=(ans*x)%maxd; x=(x*x)%maxd; y>>=1; } return ans; } int main() { scanf("%s",s+1);n=strlen(s+1);fac[0]=1;invfac[0]=1; rep(i,1,n) { if ((s[i]>='a') && (s[i]<='z')) cnt[s[i]-'a'+1]++; else cnt[s[i]-'A'+1+26]++; fac[i]=(fac[i-1]*i)%maxd; } invfac[n]=qpow(fac[n],maxd-2); per(i,n-1,1) invfac[i]=invfac[i+1]*(i+1)%maxd; dp[0]=1; rep(i,1,52) { if (!cnt[i]) continue; per(j,n/2,cnt[i]) dp[j]=(dp[j]+dp[j-cnt[i]])%maxd; } rep(i,1,52) { rep(j,i,52) { rep(k,0,n/2) tmp[k]=dp[k]; rep(k,cnt[i],n/2) tmp[k]=(tmp[k]-tmp[k-cnt[i]]+maxd)%maxd; if (i!=j) rep(k,cnt[j],n/2) tmp[k]=(tmp[k]-tmp[k-cnt[j]]+maxd)%maxd; same[i][j]=tmp[n/2]; } } ll w=fac[n/2]*fac[n/2]%maxd; rep(i,1,52) w=w*invfac[cnt[i]]%maxd; q=read(); while (q--) { int x=read(),y=read(); if ((s[x]>='a') && (s[x]<='z')) x=s[x]-'a'+1; else x=s[x]-'A'+1+26; if ((s[y]>='a') && (s[y]<='z')) y=s[y]-'a'+1; else y=s[y]-'A'+1+26; if (x>y) swap(x,y); printf("%lld\n",w*same[x][y]*2%maxd); } return 0; }
看起來一臉虛樹的樣子可是我不費啊qwq
通常的tree dp是記$dp[u][i]$表示以$u$爲根的子樹分紅$i$組的方案數,而後你發現這顆樹連根都不肯定,使用虛樹並非很容易維護(其實就是我不費)
那麼考慮這只是序列上的問題呢?記$dp[i][j]$爲前$i$個數分紅$j$組的方案數,那麼$dp[i][j]=dp[i-1][j]*(j-f[i])+dp[i-1][j-1]$,$f[i]$表示$i$前面的數中有多少個不能和$i$分在一塊兒
注意到這麼$dp$的一個問題是咱們要肯定一個合適的$dp$順序使得$i$只會被它前面的數所影響
首先爲了把樹上問題轉化到序列上咱們確定是會考慮$dfs$序的,考慮$f[i]$只表明$i$到根之間不能和$i$共處一組的點的個數,那麼這個能夠直接樹剖求解,故咱們只要使得在咱們肯定的$dp$順序中兒子節點不會出如今根節點以前便可
這看起來能夠用$LCT$,實際上咱們直接按照$f[i]$排序便可由於祖先節點的$f$必定比兒子節點的小
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) #define maxd 1000000007 typedef long long ll; const int N=100000; const double pi=acos(-1.0); struct node{ int to,nxt; }sq[200200]; int all=0,head[100100],n,q,a[101000],f[101000],seg[800400],dep[100100], fa[100100],son[100100],siz[100100],tp[100100],tot=0,dfn[100100]; ll dp[2][100100]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } void add(int u,int v) { all++;sq[all].to=v;sq[all].nxt=head[u];head[u]=all; } void dfs1(int u,int fu) { dep[u]=dep[fu]+1;fa[u]=fu;siz[u]=1; int i,maxson=0; for (i=head[u];i;i=sq[i].nxt) { int v=sq[i].to; if (v==fu) continue; dfs1(v,u); siz[u]+=siz[v]; if (siz[v]>maxson) {maxson=siz[v];son[u]=v;} } } void dfs2(int u,int tpu) { tp[u]=tpu;dfn[u]=(++tot); if (!son[u]) return; dfs2(son[u],tpu); int i; for (i=head[u];i;i=sq[i].nxt) { int v=sq[i].to; if ((v==fa[u]) || (v==son[u])) continue; dfs2(v,v); } } void modify(int id,int l,int r,int pos,int val) { seg[id]+=val; if (l==r) return; int mid=(l+r)>>1; if (pos<=mid) modify(id<<1,l,mid,pos,val); else modify(id<<1|1,mid+1,r,pos,val); } int query(int id,int l,int r,int nowl,int nowr) { if ((l>=nowl) && (r<=nowr)) return seg[id]; int ans=0,mid=(l+r)>>1; if (nowl<=mid) ans+=query(id<<1,l,mid,nowl,nowr); if (nowr>=mid+1) ans+=query(id<<1|1,mid+1,r,nowl,nowr); return ans; } int query_range(int u,int v) { int ans=0; while (tp[u]!=tp[v]) { if (dep[tp[u]]<dep[tp[v]]) swap(u,v); ans+=query(1,1,n,dfn[tp[u]],dfn[u]); u=fa[tp[u]]; } if (dep[u]>dep[v]) swap(u,v); ans+=query(1,1,n,dfn[u],dfn[v]); return ans; } void init() { n=read();q=read(); rep(i,1,n-1) { int u=read(),v=read(); add(u,v);add(v,u); } dfs1(1,0);dfs2(1,1); } void work() { while (q--) { int k=read(),m=read(),rt=read(); rep(i,1,k) { a[i]=read(); modify(1,1,n,dfn[a[i]],1); } int flag=0; rep(i,1,k) { f[i]=query_range(a[i],rt)-1; if (f[i]>=m) {flag=1;break;} } rep(i,1,k) modify(1,1,n,dfn[a[i]],-1); if (flag) {puts("0");continue;} sort(f+1,f+1+k); int now=1,pre=0; dp[0][0]=1; rep(i,1,m) dp[0][i]=0; rep(i,1,k) { rep(i,0,m) dp[now][i]=0; per(j,min(i,m),0) { if (j<=f[i]) dp[now][j]=0; else dp[now][j]=(dp[pre][j]*(j-f[i])+dp[pre][j-1])%maxd; } now^=1;pre^=1; } ll ans=0; rep(i,1,m) ans=(ans+dp[pre][i])%maxd; printf("%lld\n",ans); } } int main() { init(); work(); return 0; }
將詢問離線,記$f_i$爲$i$爲左端點時的答案,從左到右遍歷右端點$j$,每次更新$1\text~j-1$的答案,確定不能暴力更新
考慮使用線段樹維護$f$數組,更新時沿途記錄當前的最優答案$now$
記當前更新的區間爲$[l,r]$,若是這個區間不存在$a_j-now\text~a_j+now$的數的話就說明當前的$a_j$不會對該區間的答案產生影響,能夠不用繼續更新下去
可是咱們又注意到$\forall p<q$,應該有$f_p\leq f_q$,由於$[p,n]$確定包含了$[q,n]$,故更新時應先更新右區間再更新左區間防止$now$的值出現問題,能夠證實此方法的時間複雜度是$O(nlogn^2)$的
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) #define maxd 1000000007 typedef long long ll; const int N=100000; const double pi=acos(-1.0); struct node{ int ans; vector<int> num; }seg[800800]; struct qnode{ int l,r,id; }q[300300]; bool operator<(const qnode &p,const qnode &q) { return p.r<q.r; } int n,Q,a[100100],ans[300300]; vector<int>::iterator it; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } void build(int id,int l,int r) { rep(i,l,r) seg[id].num.push_back(a[i]); sort(seg[id].num.begin(),seg[id].num.end()); seg[id].ans=maxd; if (l==r) return; int mid=(l+r)>>1; build(id<<1,l,mid); build(id<<1|1,mid+1,r); rep(i,0,r-l-1) seg[id].ans=min(seg[id].ans,seg[id].num[i+1]-seg[id].num[i]); } int query(int id,int l,int r,int ql,int qr) { if ((l>=ql) && (r<=qr)) return seg[id].ans; int mid=(l+r)>>1,ans=maxd; if (ql<=mid) ans=min(ans,query(id<<1,l,mid,ql,qr)); if (qr>=mid+1) ans=min(ans,query(id<<1|1,mid+1,r,ql,qr)); return ans; } void modify(int id,int l,int r,int qr,int val,int &nowans) { if (l==r) { seg[id].ans=min(seg[id].ans,abs(seg[id].num[0]-val)); nowans=min(nowans,seg[id].ans); return; } it=lower_bound(seg[id].num.begin(),seg[id].num.end(),val); if (((it==seg[id].num.begin()) || (*(it-1)<=val-nowans)) && ((it==seg[id].num.end()) || (*it>=val+nowans))) { nowans=min(nowans,query(id,l,r,l,qr)); return; } int mid=(l+r)>>1; if (qr>=mid+1) modify(id<<1|1,mid+1,r,qr,val,nowans); modify(id<<1,l,mid,qr,val,nowans); seg[id].ans=min(seg[id<<1].ans,seg[id<<1|1].ans); } int main() { n=read(); rep(i,1,n) a[i]=read(); build(1,1,n); Q=read(); rep(i,1,Q) {q[i].l=read();q[i].r=read();q[i].id=i;} sort(q+1,q+1+Q); int nowr=1; rep(i,1,Q) { while (nowr<q[i].r) {int tmp=maxd;modify(1,1,n,nowr,a[nowr+1],tmp);nowr++;} ans[q[i].id]=query(1,1,n,q[i].l,q[i].r); } rep(i,1,Q) printf("%d\n",ans[i]); return 0; }
不是很懂這題爲何要出成交互,感受比即時戰略還不像交互
詢問$[0,10]$的值,拉格朗日插值獲得這個多項式,再枚舉$[11,maxd-1]$獲得其相應的值判斷是否爲$0$便可
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) #define maxd 1000003 typedef long long ll; const int N=200000; const double pi=acos(-1.0); ll x[20],fac[N+100],invfac[N+100]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } int query(int a) { printf("? %d\n",a);fflush(stdout); x[a]=read(); return (x[a]==0); } void answer(int ans) { printf("! %d\n",ans);fflush(stdout); } int getval(int a) { ll ans=0; rep(i,0,10) { ll now=x[i]; rep(j,0,10) if (i!=j) now=(now*(a-j))%maxd; now=now*invfac[i]*invfac[10-i]%maxd; if ((10-i)&1) ans=(ans+maxd-now)%maxd; else ans=(ans+now)%maxd; } return ans; } ll qpow(ll x,ll y) { ll ans=1; while (y) { if (y&1) ans=(ans*x)%maxd; x=(x*x)%maxd; y>>=1; } return ans; } int main() { fac[0]=1;invfac[0]=1; rep(i,1,N) fac[i]=(fac[i-1]*i)%maxd; invfac[N]=qpow(fac[N],maxd-2); per(i,N-1,1) invfac[i]=(invfac[i+1]*(i+1))%maxd; rep(i,0,10) { bool zero=query(i); if (zero) { answer(i); return 0; } } rep(i,11,maxd-1) { int now=getval(i); if (!now) {answer(i);return 0;} } answer(-1); return 0; }
線段樹維護每一個數是否須要取其相反數
分類討論
$s$爲$>$時
$s$爲$<$時
線段樹維護區間賦值和區間反轉便可
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) #define maxd 1000000007 typedef long long ll; const int N=100000; const double pi=acos(-1.0); int n,q,a[100100],seg[1700000],rev[1700000],tag[1700000]; char s[10]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } void pushdown(int id,int l,int r) { if (tag[id]!=-1) { seg[id<<1]=tag[id]; seg[id<<1|1]=tag[id]; tag[id<<1]=tag[id]; tag[id<<1|1]=tag[id]; rev[id<<1]=0;rev[id<<1|1]=0; tag[id]=-1; } if (rev[id]) { rev[id<<1]^=1;rev[id<<1|1]^=1; seg[id<<1]^=1;seg[id<<1|1]^=1; rev[id]=0; } } void modify_val(int id,int l,int r,int ql,int qr,int val) { if (ql>qr) return; pushdown(id,l,r); if ((l>=ql) && (r<=qr)) { seg[id]=val;tag[id]=val; return; } int mid=(l+r)>>1; if (ql<=mid) modify_val(id<<1,l,mid,ql,qr,val); if (qr>mid) modify_val(id<<1|1,mid+1,r,ql,qr,val); } void modify_rev(int id,int l,int r,int ql,int qr) { if (ql>qr) return; pushdown(id,l,r); if ((l>=ql) && (r<=qr)) { seg[id]^=1;rev[id]=1; return; } int mid=(l+r)>>1; if (ql<=mid) modify_rev(id<<1,l,mid,ql,qr); if (qr>mid) modify_rev(id<<1|1,mid+1,r,ql,qr); } int query(int id,int l,int r,int pos) { pushdown(id,l,r); if (l==r) return seg[id]; int mid=(l+r)>>1; if (pos<=mid) return query(id<<1,l,mid,pos); else return query(id<<1|1,mid+1,r,pos); } int main() { n=read();q=read(); rep(i,1,n) a[i]=read(); memset(tag,-1,sizeof(tag)); while (q--) { scanf("%s",s);int x=read(); if (s[0]=='>') { if (x>=0) { modify_val(1,-N,N,x+1,N,1); modify_val(1,-N,N,-N,-x-1,0); } else { modify_val(1,-N,N,-x,N,1); modify_val(1,-N,N,-N,x,0); modify_rev(1,-N,N,x+1,-x-1); } } else { if (x<=0) { modify_val(1,-N,N,-N,x-1,1); modify_val(1,-N,N,-x+1,N,0); } else { modify_val(1,-N,N,x,N,0); modify_val(1,-N,N,-N,-x,1); modify_rev(1,-N,N,-x+1,x-1); } } } rep(i,1,n) { if (query(1,-N,N,a[i])) a[i]*=-1; printf("%d ",a[i]); } return 0; }
記$dp_{u,0/1}$表示以$u$爲根的子樹,不選/選父親的方案數,最終答案就是$dp_{1,0}$
對每個點再記$f_{i,0/1/2}$表示$i$節點不選兒子/選一個兒子/選兩個以上兒子的方案
對於一個葉子結點,很明顯$dp_{u,0}=dp_{u,1}=1$
對於一個非葉子節點,若$f$數組已經計算好了,咱們有$dp_{u,0}=f_{u,0}+f_{u,2},dp_{u,1}=f_{u,1}+f_{u,2}$。緣由以下:
1)當一個點不選兒子的時候,它不可能出如今全部葉子集合中的最小聯通子圖
2)當一個點只選一個兒子的時候,父親節點必然起到了鏈接兩個葉子結點的做用,不然根據最小聯通子圖的定義咱們是不會選父親節點的
3)當一個點選了兩個以上的兒子時,它自己就能夠構成一個最小聯通子圖,他還能夠鏈接其它的葉子構成新的聯通子圖,因此對兩邊都有貢獻
故問題轉爲如何求$f$
考慮某個點$u$的一個兒子$v$,它對父親的兒子數量至多有$1$的貢獻,暴力枚舉$dp$和$f$的第二維進行轉移便可
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define fir first #define sec second #define rep(i,a,b) for (register int i=a;i<=b;i++) #define per(i,a,b) for (register int i=a;i>=b;i--) #define maxd 998244353 typedef long long ll; const int N=100000; const double pi=acos(-1.0); int n; ll dp[200200][2]; vector<int> sq[200200]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } void dfs(int u) { int len=sq[u].size(); ll f[4],tmp[4]; if (!len) {dp[u][0]=1;dp[u][1]=1;return;} f[0]=1;f[1]=0;f[2]=0; rep(i,0,len-1) { int v=sq[u][i]; dfs(v); memset(tmp,0,sizeof(tmp)); rep(j,0,2) rep(k,0,1) { int now=min(j+k,2); tmp[now]=(tmp[now]+f[j]*dp[v][k]%maxd)%maxd; } f[0]=tmp[0];f[1]=tmp[1];f[2]=tmp[2]; } dp[u][0]=(f[0]+f[2])%maxd; dp[u][1]=(f[1]+f[2])%maxd; } int main() { n=read(); rep(i,2,n) { int u=read(); sq[u].push_back(i); } dfs(1); printf("%lld",dp[1][0]); return 0; }
考慮$kruskal$的過程:先加入邊權較小的邊,再加入邊權較大的邊。咱們依照這一過程,先加入這些邊權較小的邊,這樣原圖就造成了若干個聯通塊,很明顯在同一個聯通塊中的點連邊是不會用到邊權大的邊的
根據這一點咱們就有一個狀壓+最短路的思路:$dis[v][sta]$表示點$1$到點$v$且通過的聯通塊的狀態是$sta$時的方案數,可是這樣的話點個規模達到了$O(n2^n)$,沒法接受
考慮減小狀壓的狀態數,那麼就要尋找哪一些信息是不須要記錄的。考慮一個聯通塊的大小對最短路轉移的影響,當塊的大小$\leq 3$時,咱們不可能從一個聯通塊中的一點出發,沿着邊權較大的邊,走回這個聯通塊,由於從某個聯通塊走出去又走回來至少會走兩條邊權較大的邊(記大邊權爲$b$,小邊權爲$a$),即從外面走的話至少須要走$2b$的權值,而直接從聯通塊內部走的話至多走$2a$的權值,故咱們的最短路轉移時必定不會重複遍歷一個大小$\leq 3$的聯通塊,這能夠幫助減小狀壓信息,規模被降至$O(n2^{\frac{n}{4}})$,能夠接受
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define sqr(x) (x)*(x) #define fir first #define sec second #define rep(i,a,b) for (register int i=a;i<=b;i++) #define per(i,a,b) for (register int i=a;i>=b;i--) #define maxd 1000000007 #define eps 1e-6 typedef long long ll; const int N=100000; const double pi=acos(-1.0); struct hnode{ int u,dis,s; }; bool operator <(const hnode &p,const hnode &q) { return p.dis>q.dis; } priority_queue<hnode> q; struct node{ int to,nxt,cost; }sq[20020]; int all=0,head[10010]; int n,m,col[100],dis[80][150000],cnt=0,fa[100],siz[100],a,b; bool vis[75][150000]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } void add(int u,int v,int w) { all++;sq[all].to=v;sq[all].nxt=head[u];sq[all].cost=w;head[u]=all; } int find(int x) { if (fa[x]==x) return fa[x]; fa[x]=find(fa[x]); return fa[x]; } int main() { n=read();m=read();a=read();b=read(); rep(i,1,n) {fa[i]=i;siz[i]=1;} rep(i,1,m) { int u=read(),v=read(),w=read(); add(u,v,w);add(v,u,w); if (w==a) { int fx=find(u),fy=find(v); if (fx!=fy) { fa[fx]=fy;siz[fy]+=siz[fx]; } } } memset(col,-1,sizeof(col)); rep(i,1,n) { if ((col[i]==-1) && (siz[find(i)]>3)) { int fx=find(i); rep(j,1,n) if (find(j)==fx) col[j]=cnt; cnt++; } } rep(i,1,n) rep(j,0,(1<<cnt)) dis[i][j]=maxd; if (col[1]!=-1) {q.push((hnode){1,0,1<<col[1]});dis[1][1<<col[1]]=0;} else {q.push((hnode){1,0,0});dis[1][0]=0;} while (!q.empty()) { hnode now=q.top();q.pop(); if (vis[now.u][now.s]) continue; vis[now.u][now.s]=1; int i; for (i=head[now.u];i;i=sq[i].nxt) { int v=sq[i].to; if (sq[i].cost==a) { if (dis[now.u][now.s]+sq[i].cost<dis[v][now.s]) { dis[v][now.s]=dis[now.u][now.s]+sq[i].cost; q.push((hnode){v,dis[v][now.s],now.s}); } } else { if ((find(now.u)==find(v)) || ((col[v]!=-1) && ((now.s>>col[v])&1))) continue; int tmp=now.s; if (col[v]!=-1) tmp|=(1<<col[v]); if (dis[now.u][now.s]+sq[i].cost<dis[v][tmp]) { dis[v][tmp]=dis[now.u][now.s]+sq[i].cost; q.push((hnode){v,dis[v][tmp],tmp}); } } } } rep(i,1,n) { int ans=maxd; rep(j,0,(1<<cnt)) ans=min(ans,dis[i][j]); printf("%d ",ans); } return 0; }
一看數據這麼大就知道要上數據結構
咱們擴充這個括號序列,在每一個括號的右側記錄當前所在的節點編號,以下面這棵樹咱們記作 $1(2(3)2(4)2)1(5)1$
首先,若是咱們把$($看作$+1$,$)$看作$-1$,那麼對於數字序列的某一個數$x$,它的$dep$即爲該數所在的序列以前的全部括號的對應數之和,這個由操做定義不可貴到
對於咱們擴充後的序列,咱們取下一個區間$[l,r]$,注意到節點$l$和節點$r$的$lca$必定在$[l,r]$中,而且是該區間的全部節點中深度最小的(不然咱們的$)$這個返回操做變得毫無心義)
咱們考慮直徑是什麼,樹的直徑就是找到兩個點使得$dep_u+dep_v-2dep_{lca}$儘量的大,這個東西映射到括號序列上,其實就是要找到一個三元組$(x,y,z)$使得$dep_x+dep_z-2dep_y$,由於當$x、z$肯定時使得該式的值最大的$y$就是$x,z$的$lca$
再將這個式子轉化一下:$(dep_z-dep_y)-(dep_y-dep_x)$,因此咱們能夠考慮經過維護區間內$dep_y-dep_x$的最大值來獲得答案,而每個$dep$其實就是一段區間和,因而能夠利用最大子段和問題的思想,線段樹維護一段區間的最大/小的前/後綴和,前/後綴最大差值,總體最大差值、和以及區間內的最大差值(即答案)
具體實現時注意$pushup$,詳細細節見代碼
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<math.h> #include<vector> #include<queue> #include<map> #include<set> using namespace std; #define lowbit(x) (x)&(-x) #define sqr(x) (x)*(x) #define fir first #define sec second #define rep(i,a,b) for (register int i=a;i<=b;i++) #define per(i,a,b) for (register int i=a;i>=b;i--) #define maxd 1000000007 #define eps 1e-6 typedef long long ll; const int N=100000; const double pi=acos(-1.0); struct segnode{ int premax,premin,sufmax,sufmin,pred,sufd,sum,d,ans; }seg[800100]; segnode leafl=(segnode){1,0,1,0,1,1,1,1,1},leafr=(segnode){0,-1,0,-1,1,1,-1,1,1}; int n,q; char s[200200]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } segnode pushup(segnode a,segnode b) { segnode c; c.premax=max(a.premax,a.sum+b.premax); c.premin=min(a.premin,a.sum+b.premin); c.sufmax=max(b.sufmax,b.sum+a.sufmax); c.sufmin=min(b.sufmin,b.sum+a.sufmin); c.pred=max(a.pred,max(b.pred-a.sum,a.d+b.premax)); c.sufd=max(b.sufd,max(b.d-a.sufmin,b.sum+a.sufd)); c.d=max(b.d-a.sum,a.d+b.sum); c.sum=a.sum+b.sum; c.ans=max(max(a.ans,b.ans),max(a.sufd+b.premax,b.pred-a.sufmin)); return c; } void build(int id,int l,int r) { if (l==r) { if (s[l]=='(') seg[id]=leafl;else seg[id]=leafr; return; } int mid=(l+r)>>1; build(id<<1,l,mid);build(id<<1|1,mid+1,r); seg[id]=pushup(seg[id<<1],seg[id<<1|1]); } void modify(int id,int l,int r,int pos) { if (l==r) { if (s[l]=='(') seg[id]=leafl;else seg[id]=leafr; return; } int mid=(l+r)>>1; if (pos<=mid) modify(id<<1,l,mid,pos); else modify(id<<1|1,mid+1,r,pos); seg[id]=pushup(seg[id<<1],seg[id<<1|1]); } int main() { n=read();q=read();n=(n-1)*2; scanf("%s",s+1); build(1,1,n); printf("%d\n",seg[1].ans); while (q--) { int x=read(),y=read(); swap(s[x],s[y]); modify(1,1,n,x); modify(1,1,n,y); printf("%d\n",seg[1].ans); } return 0; }
每次寫這種帶結論的$dp$都顯得十分自閉
注意到第一個數固定了之後$g_i$所含有的質因子,而且因爲排列中包含$1-n$中的全部數因此必定存在方案使得$g_{i+1}$和$g_i$非$1$時$g_{i+1}$正好是$g_i$除上某一個質數
因而問題變成了如何安排排列第$1$個數使得它含有的質因子個數最多,記這個數爲$x$
首先咱們認爲這個數必定能被寫成$2^a*3^y$的形式,由於若存在一個質數$p>4$使得$p|x$,那麼將$x$改寫成$\frac{x}{p}\times4$的話更優
接下來還會有$y\leq 1$,由於若$y>1$必有$9|x$,因而和上面同樣將$x$替代成$\frac{x}{9}\times 4$便可更優
因此這樣的話$x$的值就是惟一肯定了的了,能夠大力$dp$了
記$dp_{i,j,k}$表示前$i$項,$g_i=2^j*3^k$的方案數,同時記錄$f(j,k)$表示$1-n$中$2^j\times3^k$的倍數
1)若$g_i=g_{i-1}$那麼此時填的數就是$2^j\times3^k$的倍數,可是已經用了$i-1$個,因而$dp_{i,j,k}+=dp_{i-1,j,k}\times(f(j,k)-(i-1))$
2)若$g_i*2=g_{i-1}$那麼此時第$i$位的數是$2^j\times3^k$的倍數但不是$2^{j+1}\times 3^k$的倍數,因而$dp_{i,j,k}+=dp_{i-1,j+1,k}\times(f(j,k)-f(j+1,k))$
3)若$g_i*3=g_{i-1}$,同上可得$dp_{i,j,k}+=dp_{i-1,j,k+1}\times(f(j,k)-f(j,k+1))$
初始化$dp_{i,a,0}=1$($2^a\leq n<2^{a+1}$),$dp_{1,a-1,1}=1$($2^{a-1}*3<n$)
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<math.h> #include<algorithm> #include<vector> #include<queue> #include<set> #include<map> using namespace std; typedef long long ll; #define lowbit(x) (x)&(-x) #define sqr(x) (x)*(x) #define rep(i,a,b) for (register int i=a;i<=b;i++) #define per(i,a,b) for (register int i=a;i>=b;i--) #define fir first #define sec second #define maxd 1000000007 #define eps 1e-8 int n; ll dp[2][21][2]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } ll calc(int x,int y) { int now=(1<<x); if (y) now*=3; return n/now; } int main() { n=read(); int a=0,b=0; int tmp=1; while ((tmp<<1)<=n) {tmp<<=1;a++;} tmp>>=1; if (tmp*3<=n) dp[1][a-1][1]=1; dp[1][a][0]=1; int now=0,pre=1; rep(i,2,n) { memset(dp[now],0,sizeof(dp[now])); rep(j,0,a) { rep(k,0,1) { dp[now][j][k]=(dp[now][j][k]+dp[pre][j][k]*(calc(j,k)-i+1))%maxd; if (j!=a) dp[now][j][k]=(dp[now][j][k]+dp[pre][j+1][k]*(calc(j,k)-calc(j+1,k)))%maxd; if (k!=1) dp[now][j][k]=(dp[now][j][k]+dp[pre][j][k+1]*(calc(j,k)-calc(j,k+1)))%maxd; } } now^=1;pre^=1; } ll ans=(dp[pre][0][0]+maxd)%maxd; printf("%lld\n",ans); return 0; }
首先有$|b|=|s|=n,|a|<n$,因而能夠枚舉$|a|=m$
那麼問題轉化成如今有$n+m$個$01$變量,有相同、不相同、強制賦值三個條件
把這個問題放到圖上,兩個點之間連邊表示限制,用$0$表示二者相同,$1$表示二者不一樣;對於強制賦值的條件,咱們考慮新建兩個節點表示$0$和$1$,而後將賦值條件轉化成相同或不一樣的條件便可
最後考慮答案計算,當前的圖不合法僅當出現了奇環,合法的話單就是$2^{連通塊個數-1}$,減1是由於$b$的首位必定爲1
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<vector> #include<math.h> #include<queue> #include<set> #include<map> using namespace std; typedef long long ll; #define lowbit(x) (x)&(-x) #define sqr(x) (x)*(x) #define rep(i,a,b) for (register int i=a;i<=b;i++) #define per(i,a,b) for (register int i=a;i>=b;i--) #define maxd 998244353 #define eps 1e-8 const int N=2000; struct node{ int to,nxt,cost; } sq[200200]; int all=0,head[2020]; void add(int u,int v,int w) { all++;sq[all].to=v;sq[all].nxt=head[u];sq[all].cost=w;head[u]=all; all++;sq[all].to=u;sq[all].nxt=head[v];sq[all].cost=w;head[v]=all; } int n,tot=0,dis[2020],flag; ll bin[2020]; char s[2020]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } void dfs(int u,int now) { dis[u]=now; int i; for (i=head[u];i;i=sq[i].nxt) { int v=sq[i].to; if (dis[v]!=-1) { if (dis[u]^dis[v]^sq[i].cost) flag=1; } else dfs(v,now^sq[i].cost); } } ll work(int m) { all=0;flag=0;tot=0; memset(head,0,sizeof(head)); memset(dis,-1,sizeof(dis)); rep(i,1,n/2) add(i,n-i+1,0); rep(i,1,m/2) add(i+n,m-i+1+n,0); rep(i,1,m) if (s[i]!='?') add(i,i+n,s[i]-'0'); rep(i,m+1,n) if (s[i]!='?') add(i,n+m+1+(s[i]=='1'),0); add(n,n+m+2,0);add(n+m,n+m+2,0);add(n+m+1,n+m+2,1); rep(i,1,n+m+2) if (dis[i]==-1) {tot++;dfs(i,0);} if (flag) return 0; else return bin[tot-1]; } int main() { scanf("%s",s+1); n=strlen(s+1);bin[0]=1; reverse(s+1,s+1+n); rep(i,1,N) bin[i]=(bin[i-1]*2)%maxd; ll ans=0; rep(i,1,n-1) ans=(ans+work(i))%maxd; printf("%lld",ans); return 0; }
首先用兩次詢問找到$a_i$的顏色是否與相鄰的顏色相同,這樣的話咱們能夠將整個序列縮成一個新序列,而且新序列相鄰塊顏色不一樣,每一個塊記做$pos_i$
接下來再花兩次詢問找到$pos_i$與$pos_{i+2}$是否相同,以後直接染色便可
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<vector> #include<math.h> #include<queue> #include<set> #include<map> using namespace std; typedef long long ll; #define lowbit(x) (x)&(-x) #define sqr(x) (x)*(x) #define rep(i,a,b) for (register int i=a;i<=b;i++) #define per(i,a,b) for (register int i=a;i>=b;i--) #define mp make_pair #define fir first #define sec second #define maxd 998244353 #define eps 1e-8 int n,tp,pos[100100],ans[100100]; map<pair<int,int>,int> same; vector<int> a,b,col[4]; char s[100100]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } void query() { int len=a.size(); if (!len) return; printf("Q %d ",len); rep(i,0,len-1) printf("%d %d ",a[i],b[i]); printf("\n");fflush(stdout); scanf("%s",s); rep(i,0,len-1) { same[mp(a[i],b[i])]=s[i]-'0'; same[mp(b[i],a[i])]=s[i]-'0'; } a.clear();b.clear(); } void answer() { printf("A"); rep(i,1,3) printf(" %d",col[i].size()); puts("");fflush(stdout); rep(i,1,3) { int len=col[i].size(); rep(j,0,len-1) printf("%d ",col[i][j]); puts("");fflush(stdout); } } int main() { int T=read(); while (T--) { n=read();same.clear(); memset(pos,0,sizeof(pos)); int i; for (i=1;i+1<=n;i+=2) {a.push_back(i);b.push_back(i+1);} query(); for (i=2;i+1<=n;i+=2) {a.push_back(i);b.push_back(i+1);} query(); int tp=1; pos[tp]=1; rep(i,2,n) if (!same[mp(i-1,i)]) pos[++tp]=i; for (i=1;i+2<=tp;i+=4) {a.push_back(pos[i]);b.push_back(pos[i+2]);} for (i=2;i+2<=tp;i+=4) {a.push_back(pos[i]);b.push_back(pos[i+2]);} query(); for (i=3;i+2<=tp;i+=4) {a.push_back(pos[i]);b.push_back(pos[i+2]);} for (i=4;i+2<=tp;i+=4) {a.push_back(pos[i]);b.push_back(pos[i+2]);} query(); ans[1]=1; if (tp>1) ans[2]=2; rep(i,3,tp) { if (same[mp(pos[i],pos[i-2])]) ans[i]=ans[i-2]; else ans[i]=6-ans[i-1]-ans[i-2]; } rep(i,1,3) col[i].clear(); int now=1; rep(i,1,tp) while ((now!=pos[i+1]) && (now<=n)) {col[ans[i]].push_back(now);now++;} answer(); } return 0; }
首先確定會選$Bob$由於他的第一次移動不受限制,是真正的先手
要想$Bob$必勝其實就是找到這個二分圖的一個完美匹配,使得$Bob$沿着匹配變移動,而$Alice$因爲$Increase$和$Decrease$的限制沒法沿着匹配邊移動便可
不失通常性,咱們假設$Alice$選擇$Increase$且將棋子放在左側
考慮一個完美匹配如何不合法:僅當完美匹配中存在兩條邊$(w,x)$和$(y,z)$使得$val(w,x)<val(x,y)<val(y,z)$,由於這樣的話當$Bob$走$(w,x)$,$Alice$走$(x,y)$以後,$Bob$因爲自身$Decrease$的限制而沒法走匹配邊$(y,z)$
將從左往右的邊視爲正權邊,從右往左的邊視做負權邊,那麼這就是一個穩定婚姻問題,$O(n^2)$找匹配便可
對於其餘狀況,適當的將邊權取負便可
#include<iostream> #include<string.h> #include<string> #include<stdio.h> #include<algorithm> #include<vector> #include<math.h> #include<queue> #include<set> #include<map> using namespace std; typedef long long ll; #define lowbit(x) (x)&(-x) #define sqr(x) (x)*(x) #define rep(i,a,b) for (register int i=a;i<=b;i++) #define per(i,a,b) for (register int i=a;i>=b;i--) #define mp make_pair #define fir first #define sec second #define maxd 998244353 #define eps 1e-8 int n,tp,pos[100100],ans[100100]; map<pair<int,int>,int> same; vector<int> a,b,col[4]; char s[100100]; int read() { int x=0,f=1;char ch=getchar(); while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();} while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();} return x*f; } void query() { int len=a.size(); if (!len) return; printf("Q %d ",len); rep(i,0,len-1) printf("%d %d ",a[i],b[i]); printf("\n");fflush(stdout); scanf("%s",s); rep(i,0,len-1) { same[mp(a[i],b[i])]=s[i]-'0'; same[mp(b[i],a[i])]=s[i]-'0'; } a.clear();b.clear(); } void answer() { printf("A"); rep(i,1,3) printf(" %d",col[i].size()); puts("");fflush(stdout); rep(i,1,3) { int len=col[i].size(); rep(j,0,len-1) printf("%d ",col[i][j]); puts("");fflush(stdout); } } int main() { int T=read(); while (T--) { n=read();same.clear(); memset(pos,0,sizeof(pos)); int i; for (i=1;i+1<=n;i+=2) {a.push_back(i);b.push_back(i+1);} query(); for (i=2;i+1<=n;i+=2) {a.push_back(i);b.push_back(i+1);} query(); int tp=1; pos[tp]=1; rep(i,2,n) if (!same[mp(i-1,i)]) pos[++tp]=i; for (i=1;i+2<=tp;i+=4) {a.push_back(pos[i]);b.push_back(pos[i+2]);} for (i=2;i+2<=tp;i+=4) {a.push_back(pos[i]);b.push_back(pos[i+2]);} query(); for (i=3;i+2<=tp;i+=4) {a.push_back(pos[i]);b.push_back(pos[i+2]);} for (i=4;i+2<=tp;i+=4) {a.push_back(pos[i]);b.push_back(pos[i+2]);} query(); ans[1]=1; if (tp>1) ans[2]=2; rep(i,3,tp) { if (same[mp(pos[i],pos[i-2])]) ans[i]=ans[i-2]; else ans[i]=6-ans[i-1]-ans[i-2]; } rep(i,1,3) col[i].clear(); int now=1; rep(i,1,tp) while ((now!=pos[i+1]) && (now<=n)) {col[ans[i]].push_back(now);now++;} answer(); } return 0; }