poj 3666 Making the Grade & zoj 3512 Financial Fraud 左偏树 or dp
//poj 3666
//分析:只是在2005年集训队论文黄源河提到的题目上略微有一点点变化
1 #include"iostream" 2 #include"cstdio" 3 using namespace std; 4 const int maxn = 2100; 5 int v[maxn],l[maxn],r[maxn],d[maxn]; //节点信息 6 int N; 7 int tot,root[maxn],num_now[maxn],num_del[maxn]; //树根信息 8 9 __int64 abs(__int64 ans) 10 { 11 return ans<0?-ans:ans; 12 } 13 14 int Merge(int x,int y) 15 { 16 if(!x) //分治终点 17 return y; 18 if(!y) 19 return x; 20 if(v[x]<v[y]) 21 swap(x,y); 22 r[x] = Merge(r[x],y); //用递归函数进行分治,注意返回值 23 if(d[l[x]]<d[r[x]]) //回溯,维护数组l,r,d 24 swap(l[x],r[x]); 25 d[x] = d[r[x]]+1; 26 return x; //返回根节点 27 } 28 29 __int64 solve() 30 { 31 int i,j,k; 32 __int64 res = 0; 33 tot = 0; 34 for(i = 1; i<=N; ++i) { //将每个节点都处理成一个区间(左偏树),根节点保存这个区间的中位数信息 35 root[++tot] = i; //树根信息初始化 36 num_now[tot] = 1; //树根信息初始化 37 num_del[tot] = 0; //树根信息初始化 38 l[i] = r[i] = d[i] = 0; //节点信息初始化 39 while(tot>1&&v[root[tot-1]]>v[root[tot]]) { //循环条件为:最后一个区间的中位数小于前一区间;循环槽作为区间合并,并维护树根信息 40 root[tot-1] = Merge(root[tot-1],root[tot]); //注意Merge的返回值 41 num_now[tot-1] += num_now[tot]; 42 num_del[tot-1] += num_del[tot]; 43 --tot; 44 while(num_now[tot]>num_del[tot]+1) { 45 --num_now[tot]; 46 ++num_del[tot]; 47 root[tot] = Merge(l[root[tot]],r[root[tot]]); //注意Merge的返回值 48 } 49 } 50 } 51 for(i = k = 1; i<=tot; ++i) { 52 for(j = 1; j<=num_now[i]+num_del[i]; ++j,++k) { 53 res += abs(v[k]-v[root[i]]); 54 } 55 } 56 return res; 57 } 58 59 int main() 60 { 61 int i; 62 __int64 res_1,res_2; 63 scanf("%d",&N); 64 for(i = 1; i<=N; ++i) { 65 scanf("%d",&v[i]); 66 } 67 res_1 = solve(); 68 for(i = 1; i<=N; ++i) { 69 v[i] *= -1; 70 } 71 res_2 = solve(); 72 printf("%I64d\n",res_1<res_2?res_1:res_2); 73 return 0; 74 }
//zoj 3512
//感觉poj 3666数据太弱就去zoj 找了一道看起来一样的题目去交了,不能用__int64 ce一发,没读题wa一发,没注意数据范围re一发,不能交%I64d又wa一发,第五发才ac......
1 #include"iostream" 2 #include"cstdio" 3 using namespace std; 4 const int maxn = 50100; 5 int v[maxn],l[maxn],r[maxn],d[maxn]; //节点信息 6 int N; 7 int tot,root[maxn],num_now[maxn],num_del[maxn]; //树根信息 8 9 long long abs(long long ans) 10 { 11 return ans<0?-ans:ans; 12 } 13 14 int Merge(int x,int y) 15 { 16 if(!x) //分治终点 17 return y; 18 if(!y) 19 return x; 20 if(v[x]<v[y]) 21 swap(x,y); 22 r[x] = Merge(r[x],y); //用递归函数进行分治,注意返回值 23 if(d[l[x]]<d[r[x]]) //回溯,维护数组l,r,d 24 swap(l[x],r[x]); 25 d[x] = d[r[x]]+1; 26 return x; //返回根节点 27 } 28 29 long long solve() 30 { 31 int i,j,k; 32 long long res = 0; 33 tot = 0; 34 for(i = 1; i<=N; ++i) { //将每个节点都处理成一个区间(左偏树),根节点保存这个区间的中位数信息 35 root[++tot] = i; //树根信息初始化 36 num_now[tot] = 1; //树根信息初始化 37 num_del[tot] = 0; //树根信息初始化 38 l[i] = r[i] = d[i] = 0; //节点信息初始化 39 while(tot>1&&v[root[tot-1]]>v[root[tot]]) { //循环条件为:最后一个区间的中位数小于前一区间;循环槽作为区间合并,并维护树根信息 40 root[tot-1] = Merge(root[tot-1],root[tot]); //注意Merge的返回值 41 num_now[tot-1] += num_now[tot]; 42 num_del[tot-1] += num_del[tot]; 43 --tot; 44 while(num_now[tot]>num_del[tot]+1) { 45 --num_now[tot]; 46 ++num_del[tot]; 47 root[tot] = Merge(l[root[tot]],r[root[tot]]); //注意Merge的返回值 48 } 49 } 50 } 51 for(i = k = 1; i<=tot; ++i) { 52 for(j = 1; j<=num_now[i]+num_del[i]; ++j,++k) { 53 res += abs(v[k]-v[root[i]]); 54 } 55 } 56 return res; 57 } 58 59 int main() 60 { 61 int i; 62 long long res_1; 63 while(scanf("%d",&N)&&N) { 64 for(i = 1; i<=N; ++i) { 65 scanf("%d",&v[i]); 66 } 67 res_1 = solve(); 68 printf("%lld\n",res_1); 69 } 70 return 0; 71 }
//poj 3666,34696K内存过的,以后第一维状态还是压缩一下好了...
1 #include"iostream" 2 #include"cstdio" 3 #include"cstring" 4 #include"algorithm" 5 #include"cmath" 6 using namespace std; 7 const int maxn = 2100; 8 __int64 dp[maxn][maxn]; //第一维状态:数组 a 下标,可状压;第二维状态:排序后的数组 b 前缀集合的大小 9 int n,a[maxn],b[maxn]; //左偏树的做法是不断的取数组 a 单个元素构成的区间的中位数作为 数组 b 中的元素,若数组 b 开始下降,就从后往前做一趟区间合并 10 //而 dp 的做法则更加粗犷,因为对于 a[i] ,dp显然要枚举出所有可能做它“中位数”的 b[j],所以 dp 不关注选谁作为“中位数”,只关注枚举的顺序。以递增的顺序枚举,所得到的最优解的“中位数”在平面直角坐标系上就是一条不降的曲线 11 //第一维状态是类前缀集合的大小,第二维状态是前缀集合的大小,所以很容易得到状态转移方程为 dp[i][j] = min(dp[i-1][j]+cost,dp[i][j-1]); 12 int main() 13 { 14 int i,j; 15 scanf("%d",&n); 16 memset(dp,0x3f,sizeof(dp)); 17 memset(dp[0],0,sizeof(dp[0])); //因为第二维状态为前缀集合的大小,所以当 i==0 时整个第二维状态全部都需要初始化 18 for(i = 1; i<=n; ++i) { 19 scanf("%d",&a[i]); 20 b[i] = a[i]; 21 } 22 sort(b+1,b+1+n); 23 for(i = 1; i<=n; ++i) { 24 for(j = 1; j<=n; ++j) { 25 dp[i][j] = min(dp[i][j-1],dp[i-1][j]+abs(a[i]-b[j])); 26 } 27 } 28 printf("%I64d\n",dp[n][n]); 29 return 0; 30 }
//poj 3666,状态压缩 dp 176K内存过
1 #include"iostream" 2 #include"cstdio" 3 #include"cstring" 4 #include"algorithm" 5 using namespace std; 6 const int maxn = 2010; 7 __int64 dp[2][maxn]; 8 int n,a[maxn],b[maxn]; 9 10 int main() 11 { 12 int i,j; 13 scanf("%d",&n); 14 for(i = 1; i<=n; ++i) { 15 scanf("%d",&a[i]); 16 b[i] = a[i]; 17 } 18 sort(b+1,b+1+n); 19 bool now = 0; 20 for(i = 1; i<=n; ++i) { 21 now ^= 1; 22 memset(dp[now],0x3f,sizeof(dp[now])); 23 for(j = 1; j<=n; ++j) { 24 dp[now][j] = min(dp[now^1][j]+abs(a[i]-b[j]),dp[now][j-1]); 25 } 26 } 27 printf("%I64d\n",dp[now][n]); 28 return 0; 29 }