Ciel and Gondolas CodeForces - 321E
题意:有n个人,第i个人和第j个人放在一起时会产生a[i][j]的沮丧值(是社恐吗),保证a[i][j]=a[j][i],两个人只产生一份沮丧值。将n个人分成连续的k组,每组产生的沮丧值是这组人两两之间产生的沮丧值之和。求所有沮丧值的最小值。
解:设dp[i][j]为将前j个人分为i组,显然有dp[i][j]=min(dp[i-1][k]+sum(k,j)). 现在sum(k,j)和k有关系了,不能用单调队列优化。引入决策单调性。对于每一个dp[i][j],都会选择一个k进行转移,称k为决策点。决策单调性指后作出决策所选择的决策点,一定不在之前决策选择的决策点前面。
推荐读物:关于决策单调性优化动态规划
这道题属于上文中决策点之间不影响的情况,可以用分治实现。但一开始写了二分,然后T飞了……存个代码。
代码:
#include <bits/stdc++.h> using namespace std; #define maxx 4005 #define maxn 25 #define maxm 205 #define ll long long #define inf 1000000009 #define mod 2520 int dp[805][maxx]={0}; int sum[maxx][maxx]={0}; int q[maxx]={0}; int n,k; int read(int res = 0) {char ch = getchar(); while (!isdigit(ch)) ch = getchar(); while (isdigit(ch)) res = res * 10 + (ch - '0'), ch = getchar(); return res;} int cal(int l,int r){ return (sum[r][r]-sum[l-1][r]-sum[r][l-1]+sum[l-1][l-1])/2; } int get(int s,int e,int i){ int l=e+1,r=n; int res=0x3f3f3f3f; while(l<=r){ int mid=(l+r)/2; if(dp[i-1][s]+cal(s+1,mid)>=dp[i-1][e]+cal(e+1,mid)){ res=mid; r=mid-1; } else{ l=mid+1; } } return res; } signed main() { n=read(); k=read(); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ int w=read(); sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+w; } } for(int i=1;i<=n;i++){ dp[0][i]=0x3f3f3f3f; } for(int i=1;i<=k;i++){ int head=1,tail=1; q[1]=0; for(int j=1;j<=n;j++){ while(head<tail&&get(q[head],q[head+1],i)<=j) head++; dp[i][j]=dp[i-1][q[head]]+cal(q[head]+1,j); while(head<tail&&get(q[tail-1],q[tail],i)>=get(q[tail],j,i)) tail--; q[++tail]=j; } } printf("%d\n",dp[k][n]); return 0; } //dp[i][j]=min(dp[i-1][k]+val(k+1,j))
分治代码:
#include <bits/stdc++.h> using namespace std; #define maxx 4005 #define maxn 25 #define maxm 205 #define ll long long #define inf 1000000009 #define mod 2520 int dp[805][maxx]={0}; int sum[maxx][maxx]={0}; int n,k; inline int read(){ int x=0; char ch=getchar(); for(;ch<'0'||ch>'9';ch=getchar()); for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+ch-'0'; return x; } int cal(int l,int r){ return (sum[r][r]-sum[l-1][r]-sum[r][l-1]+sum[l-1][l-1])/2; } int ii; void solve(int l,int r,int s,int e){ if(r<l||e<s) return; if(s==e){ //printf("%d %d\n",l,r); for(int i=l;i<=r;i++){ dp[ii][i]=dp[ii-1][s]+cal(s+1,i); } return; } int mid=(l+r)/2; int ans=s; for(int i=s;i<mid&&i<=e;i++){ if(dp[ii][mid]>dp[ii-1][i]+cal(i+1,mid)){ dp[ii][mid]=dp[ii-1][i]+cal(i+1,mid); ans=i; } } solve(l,mid-1,s,ans); solve(mid+1,r,ans,e); } signed main() { n=read(); k=read(); int x; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ x=read(); sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+x; } } memset(dp,0x3f,sizeof dp); for(int i=0;i<=k;i++){ dp[i][0]=0; } for(int i=1;i<=n;i++){ dp[1][i]=cal(1,i); } for(int i=2;i<=k;i++){ ii=i; solve(1,n,0,n-1); } printf("%d\n",dp[k][n]); return 0; } //dp[i][j]=min(dp[i-1][k]+val(k+1,j))
感觉这样的优化把第一次决策预处理好会舒服一点。
Yet Another Minimization Problem CodeForces - 868F
题意:把上题的价值改为子数组内能组成多少两个元素相同的二元组,比如n个2可以组成n*(n-1)/2个这样的二元组,n不必须连续。
解:啊和上题套路一样一样的,处理val的时候用一种类似于莫队的方法,暴力加减(
代码:
#include <bits/stdc++.h> using namespace std; #define maxx 100005 #define maxn 25 #define maxm 205 #define ll long long #define inf 1000000009 #define mod 2520 ll dp[25][maxx]={0}; int a[maxx]={0}; int n,k; ll cnt[maxx]={0}; ll ans=0; int L=1,R=1; void upd(int c,int d){ ans+=d*cnt[c]*(cnt[c]-1)/2; } ll cal(int l,int r){ while(L<l) upd(a[L],-1),cnt[a[L]]--,upd(a[L],1),L++; while(L>l) L--,upd(a[L],-1),cnt[a[L]]++,upd(a[L],1); while(R>r) upd(a[R],-1),cnt[a[R]]--,upd(a[R],1),R--; while(R<r) R++,upd(a[R],-1),cnt[a[R]]++,upd(a[R],1); return ans; } int ii; void solve(int l,int r,int s,int e){ if(l>r||s>e) return; if(s==e){ for(int i=l;i<=r;i++){ dp[ii][i]=dp[ii-1][s]+cal(s+1,i); } return; } int mid=(l+r)/2,p=l; for(int i=s;i<mid&&i<=e;i++){ if(dp[ii][mid]>dp[ii-1][i]+cal(i+1,mid)){ dp[ii][mid]=dp[ii-1][i]+cal(i+1,mid); p=i; } } solve(l,mid-1,s,p); solve(mid+1,r,p,e); } signed main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); } cnt[a[1]]++; memset(dp,0x3f,sizeof dp); for(int i=1;i<=n;i++){ dp[1][i]=cal(1,i); } for(int i=0;i<=k;i++){ dp[0][i]=0; } for(int i=2;i<=k;i++){ ii=i; solve(1,n,0,n-1); } printf("%lld\n",dp[k][n]); return 0; } //dp[i][j]=min(dp[i-1][k]+val(k+1,j))