题意
? 这嘛东西,\(n^2\)自己写去
\[\ \]
\[\ \]
感觉自己智力被吊打
\(dp[i][j]\)表示 , 对于当前的一个空栈 , \(i\)到\(j\)这一段都出栈的最小花费
显然是长得一副区间(诡)dp(异)的样子 , 如何转移呢?(建议自己想想吧)
对于一个\(dp[i][j]\),因为这个\(i\)必须是最先入栈的 , 所以我们可以枚举它的出栈时间\(k\) , 那么总贡献就是
\[dp[i+1][k]+(k-i)*a[i]+sum[k+1..j] \cdot (k-i+1)\]
即先出栈的\(i+1...k\)的贡献和+i自己的贡献+\(k+1..j\)这一段的贡献和它们被推迟\(k-i+1\)位产生的贡献
rep(i,1,n) rep(j,i+1,n) dp[i][j]=1e9; rep(i,1,n) dp[i][i]=a[i]; drep(i,n,1) rep(j,i,n) rep(k,i,j) dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]+(k-i)*a[i]+(k-i+1)*(s[j]-s[k]));\[\ \]
\[\ \]
where is my head?
每个人的决策其实就是把一群人分成一组,所以可以直接一个个区间接上去
细节:满足条件的人大于\(R-L+1\)时要取\(min\)
区间dp?
rep(i,1,n) a[i]=rd(),b[i]=rd(); rep(i,0,n) rep(j,0,n) c[i][j]=0; rep(i,0,n) dp[i]=0; rep(i,1,n) c[a[i]][n-b[i]]++; rep(i,0,n-1) rep(j,i+1,n) dp[j]=max(dp[j],dp[i]+min(j-i,c[i][j])); printf("%d\n",dp[n]);又是假区间dp
const int N=1010; const double eps=1e-6; int n,m; map <int,double> M; typedef map<int,double> ::iterator iter; int p[N]; double x[N]; int cnt; double dp[N][51];//前i个,放j个站 double sum[N][N]; int main(){ while(~scanf("%d%d",&n,&m)&&n&&m) { M.clear(); rep(i,1,n) { rep(j,1,rd()) { int p=rd(); double x;scanf("%lf",&x); M[p]+=x; } } cnt=0; for(iter it=M.begin();it!=M.end();++it) { p[++cnt]=it->first; x[cnt]=it->second; }//暴力离散 double ans=1e11; rep(i,0,cnt) rep(j,0,m) dp[i][j]=1e11; rep(i,1,cnt) rep(j,1,cnt) sum[i][j]=sum[i][j-1]+x[j]*abs(p[i]-p[j]);//预处理便于算贡献 rep(i,1,cnt) dp[i][1]=sum[i][i]; rep(i,1,cnt) { rep(j,1,m-1) if(dp[i][j]<1e10) { rep(k,i+1,cnt) { int mid=(p[i]+p[k])>>1; mid=lower_bound(p+1,p+cnt+1,mid)-p; double s=min(sum[i][mid]-sum[i][i]+sum[k][k]-sum[k][mid],sum[i][mid-1]-sum[i][i]+sum[k][k]-sum[k][mid-1]);//贡献 dp[k][j+1]=min(dp[k][j+1],dp[i][j]+s); } } } rep(i,1,cnt) ans=min(ans,dp[i][m]+sum[i][cnt]-sum[i][i]); printf("%.2lf\n",ans); } }\[ \ \]
\[ \ \]
假区间dp again
\(dp[l1][r1][l2][r2]\)表示两个序列分别还剩下\(l1,r1\),\(l2,r2\)的答案 , 很基础但是有细节
int n,m; int a[N],b[N]; int dp[N][N][N][N]; int sum1[N],sum2[N]; int main(){ rep(kase,1,rd()) { n=rd(); rep(i,1,n) sum1[i]=a[i]=rd(),sum1[i]+=sum1[i-1]; rep(i,1,n) sum2[i]=b[i]=rd(),sum2[i]+=sum2[i-1]; memset(dp,0,sizeof dp); drep(l1,n+1,1) rep(r1,l1-1,n) drep(l2,n+1,1) rep(r2,l2-1,n) { if(l1>r1&&l2>r2) continue; int s=1e9; if(l1<=r1) s=min(s,min(dp[l1+1][r1][l2][r2],dp[l1][r1-1][l2][r2])); if(l2<=r2) s=min(s,min(dp[l1][r1][l2+1][r2],dp[l1][r1][l2][r2-1])); dp[l1][r1][l2][r2]=sum1[r1]-sum1[l1-1]+sum2[r2]-sum2[l2-1]-s; } printf("%d\n",dp[1][n][1][n]); } }\[ \ \]
\[ \ \]
这个鬼题是真的惊了
首先肯定要接两倍数组断成链
然后我们分析题目性质,对于任意路径,互为重合逆子序列时最优
即跳跃路径为原序列的一个回文子序列时一定更优
若两只兔子的路径错开,那么一定可以向两边继续延伸
细节:一个点时回文长度为1,当跳跃总长小于n时答案要加一
int n,m; int a[N]; int dp[N][N]; int main(){ srand(618); while(~scanf("%d",&n)&&n){ rep(i,1,n) a[i]=a[i+n]=rd(); int ans=0; memset(dp,0,sizeof dp); rep(l,1,n) drep(i,n*2-l+1,1) { int j=i+l-1; if(a[i]==a[j]) dp[i][j]=dp[i+1][j-1]+2-(i==j); else dp[i][j]=max(dp[i+1][j],dp[i][j-1]); ans=max(ans,dp[i][j]+(l<n)); } printf("%d\n",ans); } }\[ \ \]
\[ \ \]
这个题长得还是比较区间dp的
表示自己的转移错得离谱都过掉了。。。
正确的转移方法和T2有点类似,即枚举最后一个杀死的狼,加上前后直接转移
int n,m; int a[N],b[N]; int dp[N][N]; int main(){ rep(kase,1,rd()){ rep(i,1,n=rd()) a[i]=rd(); rep(i,1,n) b[i]=rd();b[n+1]=a[n+1]=0; //memset(dp,10,sizeof dp); drep(i,n,1) rep(j,i,n) { dp[i][j]=1e9; if(i==j) dp[i][j]=a[i]+b[i-1]+b[i+1]; else rep(k,i,j) dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]); //第k只狼最后杀死,贡献分开算 } printf("Case #%d: %d\n",kase,dp[1][n]); } }\[ \ \]
\[ \ \]
这个题令我不想说话
int n,m; int a[N],b[N],cnt; int s[N],ans[N][N]; void Add(int p,int x){ while(p<=n) s[p]+=x,p+=p&-p; } int Que(int p){ int res=0; while(p) res+=s[p],p-=p&-p; return res; } int main(){ n=rd(),m=rd(); rep(i,1,n) a[i]=b[i]=rd(); sort(b+1,b+n+1),cnt=unique(b+1,b+n+1)-b-1; rep(i,1,n) a[i]=lower_bound(b+1,b+cnt+1,a[i])-b; rep(l,1,n) { memset(s,0,sizeof s); rep(r,l,n) { ans[l][r]=ans[l][r-1]+r-l-Que(a[r]); Add(a[r],1); } } rep(i,1,m) { int x=rd(),y=rd(); printf("%d\n",ans[x][y]); } }首先要理解题意
所以符号是任意安排顺序,每个符号相当于合并两个数
所以总方案数应该是\((n-1)!\)
转移是也要注意,应该乘上左右两个区间符号交叉出现的价值
将区间\([i,k];[k+1,j]\)合并至\([i,j]\)时要乘上的贡献是
\[\frac{(j-i-1)!}{(k-i)!*(j-k-1)!}\]
(因为固定了第\(k\)个符号最后,所以是\(j-i-1\))
int n,m; int a[N],opt[N]; ll g[N][N]; ll fac[N]={1},Inv[N]={1,1}; int main(){ rep(i,1,N-1) fac[i]=fac[i-1]*i%P; rep(i,2,N-1) Inv[i]=1ll*(P-P/i)*Inv[P%i]%P; rep(i,1,N-1) Inv[i]=Inv[i]*Inv[i-1]%P; while(~scanf("%d",&n)) { rep(i,1,n) a[i]=rd(); rep(i,1,n-1) { char ch; do ch=getchar(); while(ch!='+'&&ch!='-'&&ch!='*'); if(ch=='+') opt[i]=1; else if(ch=='-') opt[i]=2; else opt[i]=3; } memset(g,0,sizeof g); drep(i,n,1) rep(j,i,n) { if(i==j) g[i][i]=a[i];//边界条件 rep(k,i,j-1) { ll t=Inv[k-i]%P*Inv[j-k-1]%P*fac[j-i-1]%P; if(opt[k]==1) { g[i][j]=(g[i][j]+(fac[k-i]*g[k+1][j]%P+fac[j-k-1]*g[i][k]%P)*t)%P; } else if(opt[k]==2) { g[i][j]=(g[i][j]+(-1ll*fac[k-i]*g[k+1][j]%P+1ll*fac[j-k-1]*g[i][k]%P)*t)%P; } else { g[i][j]=(g[i][j]+g[i][k]*g[k+1][j]%P*t)%P; } } } printf("%lld\n",(g[1][n]%P+P)%P);//可能有负数 } }\[ \ \]
\[ \ \]
众所周知我是一个擅长乱搞的人,所以这个奇怪的题我也是乱搞搞过去的
它长得并不是一个标准的区间dp
两种决策:
直接从左右端点向两边扩展 ;
将这段区间切开
注意由于求得是整个序列的答案所以最后还要合并一下
int n,m; int a[N],b[N]; ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); } ll dp[N][N]; int main(){ rep(kase,1,rd()){ n=rd(); rep(i,1,n) a[i]=rd(); rep(i,1,n) b[i]=rd(); memset(dp,-63,sizeof dp); drep(i,n,1) rep(j,i+1,n) { if(j-i==1) { if(gcd(a[i],a[j])!=1) dp[i][j]=b[i]+b[j]; continue; } if(gcd(a[i],a[j])!=1) dp[i][j]=max(dp[i][j],dp[i+1][j-1]+b[i]+b[j]);//决策一 rep(k,i,j) dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);//决策二 } ll ans=0; rep(i,1,n) { dp[1][i]=max(dp[1][i],0ll); rep(j,1,i) dp[1][i]=max(dp[1][i],max(0ll,dp[1][j])+max(0ll,dp[j+1][i])); }//合并答案 rep(i,1,n) rep(j,i,n) ans=max(ans,dp[i][j]); printf("%lld\n",ans); } }\[ \ \]
\[ \ \]
跑错的尺取
const int N=5100,P=1e9+7; int n,m; char s[N]; int dp[N]; int main(){ rep(kase,1,rd()){ m=rd(); scanf("%s",s+1);n=strlen(s+1); int ans=0; rep(i,3,n*2-1) { reg int cnt=0,p=1; drep(j,i/2-(!(i&1)),1) { if(i-j>n) break; cnt++; dp[cnt]=dp[cnt-1]+abs(s[j]-s[i-j]); while(dp[cnt]-dp[p-1]>m) p++; ans=max(ans,cnt-p+1); } } printf("%d\n",ans); } }\[ \ \]
\[ \ \]
这个题没有素质!它卡常!
\(dp[i][j][0...4]\)表示
0:消完了
1:还剩1个0
2:还剩2个0
3:还剩1个1
4:还剩2个1
转移极其繁琐
卡常技巧:相邻相同的可以压成一个块
(希望我没有想错吧)
const int N=410; #define chk(a,b) (a>b&&(a=b))//卡常 int n,m; char s[N]; int a[N],b[N],c[N],dp[N][N][5]; //0 --> 0 //1 --> one 0 //2 --> two 0 //3 --> one 1 //4 --> two 1 int main(){ int T; scanf("%d",&T); rep(kase,1,T){ scanf("%s",s+1); n=strlen(s+1); rep(i,1,n) a[i]=s[i]^'0'; int _n=0; rep(i,1,n) if(i==1||a[i]!=a[i-1]) { _n++; b[_n]=1; c[_n]=a[i]; } else b[_n]++,b[_n]=b[_n];//块合并 n=_n; rep(i,1,n) rep(j,1,n) rep(o,0,4) dp[i][j][o]=1e9; rep(i,1,n) { dp[i][i][c[i]*2+b[i]]=0; dp[i][i][0]=3-b[i]; }//边界条件初始化 drep(i,n,1) rep(j,i+1,n) { rep(k,i,j-1){ reg int *x=dp[i][k],*y=dp[k+1][j];//指针卡常 chk(dp[i][j][0],x[0]+y[0]); chk(dp[i][j][0],x[1]+y[2]); chk(dp[i][j][0],x[2]+y[2]); chk(dp[i][j][0],x[2]+y[1]); chk(dp[i][j][0],x[3]+y[4]); chk(dp[i][j][0],x[4]+y[3]); chk(dp[i][j][0],x[4]+y[4]); chk(dp[i][j][1],x[0]+y[1]); chk(dp[i][j][1],x[1]+y[0]); chk(dp[i][j][2],x[1]+y[1]); chk(dp[i][j][2],x[0]+y[2]); chk(dp[i][j][2],x[2]+y[0]); chk(dp[i][j][3],x[0]+y[3]); chk(dp[i][j][3],x[3]+y[0]); chk(dp[i][j][4],x[3]+y[3]); chk(dp[i][j][4],x[0]+y[4]); chk(dp[i][j][4],x[4]+y[0]); } chk(dp[i][j][0],dp[i][j][1]+2); chk(dp[i][j][0],dp[i][j][2]+1); chk(dp[i][j][0],dp[i][j][3]+2); chk(dp[i][j][0],dp[i][j][4]+1); } printf("Case #%d: %d\n",kase,dp[1][n][0]); } }(题解写到一半突然dalao来质问我,发现这种做法好像是错的。。。,但还不清楚是不是)
如果发现有错误请写评论
转载于:https://www.cnblogs.com/chasedeath/p/11331120.html
相关资源:JAVA上百实例源码以及开源项目