这套题应该是由于每一个题目的时限都比较长,所以,被杭电用来测试系统了。也就是2012 ACM/ICPC Asia Regional Online Warmup。也就是hdu 4257-4266
这套题的测试数据及代码:http://serjudging.vanb.org/?p=359
这套题,今天我做了四个,还有一个,想做没时间了!开始的时候,状态不好,浪费了很多时间!
hdu 4358 Covered Walkway 【DP + 斜率优化】
题目大意:现在有n个必须染色的点,区间【x,y】染色的费用是c+(x-y)2,其中x可以==y,问将所有必须染色的点染色的费用最小是多少?
首先可以想到的是dp做法,dp[i] = dp[j] +(pos[i]-pos[j])^2+c;
dp[i]表示前i个染色的最小费用,pos[i] 为i点的位置。
然后就是用斜率优化将n*n的复杂度降到O(n);
code:
#include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 1000010 int n,c; long long p[N],dp[N]; int que[N]; long long g_u(int j,int k) { return dp[j]-dp[k] + p[j+1]*p[j+1] -p[k+1]*p[k+1]; } long long g_d(int j,int k) { return p[j+1] - p[k+1]; } int main() { while(scanf("%d%d",&n,&c) != EOF) { if(n == 0 && c == 0) break; for(int i = 1;i <= n;i++) scanf("%I64d",&p[i]); int head = 0,tail = 0; que[tail++] = 0; dp[1] = 0; for(int i = 1;i <= n;i++) { while( head +1 < tail && g_u(que[head],que[head+1]) > 2*p[i] * g_d(que[head],que[head+1]) ) head++; dp[i] = dp[ que[head] ] + c + (p[i] - p[que[head]+1])*(p[i] - p[que[head]+1]); if(i == n) break; while(head+1 < tail && g_u(que[tail-2],que[tail-1])*g_d(que[tail-1],i) > g_u(que[tail-1],i)*g_d(que[tail-2],que[tail-1])) tail--; que[tail++] = i; } printf("%I64d\n",dp[n]); } return 0; }
题目大意:说起来比较麻烦,就是不断的放card然后再按顺序取起来,重复操作。问最少几次可以使得回到原来的顺序。
解法:首先模拟一次的操作,这样的话,就相当于产生了一次置换,然后求这个置换的循环节,所有循环节长度的最小公倍数就是答案。
这个题目比较简单,但是25秒的时限我居然超时了!!最后查到的是,memset 了一个二维数组,而其实这个二维数组只需要第二位=0的情况!靠
code;
#include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 810 int mapp[N][N]; int map[N]; bool vis[N]; long long gcd(long long a,long long b) { long long d=1; while (a&&b) if (~a&1) if (~b&1) d<<=1,a>>=1,b>>=1; else a>>=1; else if (~b&1) b>>=1; else { if (a<b) a^=b,b^=a,a^=b; long long t=b; b=(a-b)>>1; a=t; } return d*(b?b:a); } inline long long lcm(long long a,long long b){ return a/gcd(a,b)*b; } long long ree[N][N]; int main() { //freopen("transform.in","r",stdin ); //freopen("transform.out","w",stdout ); int n,k; memset(ree,-1,sizeof(ree)); while(scanf("%d%d",&n,&k) != EOF ) { if(n == 0 && k == 0) break; //if(k == 1) cout << "\n"; if(ree[n][k] != -1) { printf("%I64d\n",ree[n][k]); continue; } for(int i = 0;i < k;i++) mapp[i][0] = 0; for(int i = 0;i < n;i++) { int t = i%k; mapp[t][++mapp[t][0]] = i; } int kk = 0; for(int i = 0;i < k;i++) for(int j = mapp[i][0];j > 0;j--) map[ mapp[i][j] ] = kk++; //for(int i = 0;i < n;i++) cout << val[i] << " ";cout << "\n"; //for(int i = 0;i < n;i++) cout << map[i] << " ";cout << "\n"; long long re = 1; memset(vis,0,sizeof(vis)); for(int i = 0;i < n;i++) { if(vis[i] == 1) continue; vis[i] = 1; long long len = 1; int t = i; while(map[t] != i) { len++; //cout << i << " " << t << " : "; t = map[t] ; vis[ map[t] ] = 1; } re = lcm(re,len); //cout << "\n"; } ree[n][k] = re; //ma = max(re,ma); printf("%I64d\n",re); } //cout << ma; return 0; }
题目大意:汉诺塔 将所有东西从A移动到B,现在已经移动了一些,当然未必是按最优策略移动到当前状态的,问最少几步可以使得当前状态到所有圆盘都到B上。
题解;这个如果深入理解汉诺塔的话,应该可以解决。具体的思路就是看看当前状态到底是从哪个状态转移来的。看代码吧,比较难说明!
code;
#include <iostream> #include <stdio.h> #include <string.h> using namespace std; int map[100]; int has[4]; char str[100]; bool viss[4]; long long pow(int a,int n) { long long ret=1; long long A=a; while(n) { if (n & 1) { ret=(ret*A); } A=(A*A); n>>=1; } return ret; } long long dfs(int len,int from,int to,int a[]) { //for(int i = 0;i < 4;i++) cout << a[i] << " ";cout << "\n"; if(len == 0) return 0; long long re = 0; if( map[len] == to) { a[to]--; return dfs(len-1,map[len-1],to,a); } memset(viss,0,sizeof(viss)); int th; viss[from] = 1;viss[to] = 1; if(viss[1] == 0) th = 1; else if(viss[2] == 0) th = 2; else th = 3; if(map[len] == from) { a[from]--; return pow(2,len-1) + dfs(len-1,from,th,a); }else { a[th]--; re = pow(2,len-1) + dfs(len-1,map[len-1],th,a); } return re; } int main() { while(1) { scanf("%s",str); if(str[0] == 'X') break; int len = strlen(str); memset(has,0,sizeof(has)); for(int i = 1;i <= len;i++) { int t = str[i-1] - 'A'+1; map[i] = t; has[t]++; } int size[4] = {0,has[1],has[2],has[3]}; printf("%I64d\n",dfs(len,1,2,size ) ); } return 0; }
再补充一个 hdu 4261 Estimation 【DP】
题目大意:给定N个数,将这N个数分成连续的k端,每一段取一个B求sum = Σ|A[i]-B|,问求如何分段,使得每一段的sum再求的和最小,输出这个最小值。
解法:首先dp状态转移方程是:dp[i][j] = min(dp[i][j],dp[m][j-1] + cost[m+1][i];
dp[i][j]表示前i个分成j段的最值,cost[i][j]表示i-j区间的最优解。dp转移比较简单。
但是如何求cost却成了难点,对于每一个区间的b是中位数,不做解释,两个优先队列维护就是了,大牛都是用的堆,我用的不熟,就用了stl的优先队列
code:
#include <iostream> #include <stdio.h> #include <string.h> #include <queue> #include <stdlib.h> using namespace std; #define N 2020 int ai[N],cost[N][N]; int dp[N][30]; int n,k; int abs(unsigned int a) { if(a < 0) return -a;return a; } void deal_cost() { for(int i = 1;i <= n;i++) { priority_queue <int > ma;//从小到大的 队列里出的是大的 priority_queue<int ,vector<int> ,greater<int> > mi;//从大到小的 int sum_ma = 0,sum_mi = 0; for(int j = i;j <= n;j++) { //mi.push( ai[j] ); int size = (j-i+1+1) / 2 ; int t = ai[j]; if(mi.size() > 0 && t >= mi.top()) { mi.push(t); sum_mi += t; }else if(ma.size() > 0 && t <= ma.top()) { ma.push(t); sum_ma += t; } else { mi.push(t); sum_mi += t; } while(ma.size() < size) { int tt = mi.top(); ma.push(tt); mi.pop(); sum_ma += tt; sum_mi -= tt; } while(ma.size() > size) { int tt = ma.top(); mi.push(tt); ma.pop(); sum_ma -= tt; sum_mi += tt; } int b = ma.top(); cost[i][j] = b*ma.size() - sum_ma + sum_mi - b*mi.size() ; } } } int min(int a,int b) { if(a == -1) return b; return a < b ? a : b; } int main() { /*priority_queue <int> q; q.push(1); q.push(2); q.push(3); cout << q.top();*/ while(scanf("%d%d",&n,&k) != EOF) { if(n == 0 && k == 0) break; for(int i = 1;i <= n;i++) scanf("%d",&ai[i]); deal_cost(); ai[0] = 0; for(int i = 2;i <= n;i++) ai[i] += ai[i-1]; memset(dp,-1,sizeof(dp)); dp[0][0] = 0; for(int i = 1;i <= n;i++) { for(int j = 1;j <= k;j++) { for(int m = 0;m < i;m++) { //dp[i][j] = min(dp[i][j],dp[m][j-1] + abs( cost[m+1][i]*(i - m) - (ai[i] - ai[m]) ) ); if(dp[m][j-1] != -1) dp[i][j] = min(dp[i][j],dp[m][j-1] + cost[m+1][i] ); //cout << i << " " << j << " " << dp[i][j] << "\n"; } } } //for(int i = 1;i < k;i++) { //for(int j = 1;j <= n;j++) cout << dp[j][i] << " "; //cout << "\n"; } printf("%d\n",dp[n][k]); } return 0; }
hdu 4262 Juggler 【线段树】
这个题是我想做但是没时间的题。a了补在这儿。
题目大意:魔术,现在有n个珠子形成一个环,开始手里是1号珠子。
有三种操作,每一次操作耗时1。操作一:左旋,操作二:右旋;操作三:将手中的珠子扔掉,顺时针方向的珠子到当前手里。
给定仍掉的顺序,问最少需要多少时间可以使得按给定的顺序扔掉。
线段树的操作:单点更新,去掉手中珠子。区间查询,查询当前位置到需要扔点的珠子是左旋近还是右旋近。
code:
#include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 1000010 int map[N]; ///xian duan shu #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define fmid int m = (l+r)>>1 int sum[N*4]; void Push_up(int rt) { sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } void build(int l,int r,int rt) { if(l == r) { sum[rt] = 1; return; } fmid; build(lson); build(rson); Push_up(rt); } void update(int pos,int val,int l,int r,int rt) { if(l == r) { sum[rt] = val; return; } fmid; if(pos <= m) update(pos,val,lson); else update(pos,val,rson); Push_up(rt); } int query(int L,int R,int l,int r,int rt) { if(L <= l && r <= R) return sum[rt]; fmid; int re = 0; if(L <= m) re += query(L,R,lson); if(m < R) re += query(L,R,rson); return re; } ///ppppppppppppppp int main() { int n; while(scanf("%d",&n) != EOF && n != 0) { int t; for(int i = 1;i <= n;i++) { scanf("%d",&t); map[t] = i; } build(1,n,1); long long re = n; int now = 1; for(int i = 1;i < n;i++) { //cout << map[i] << " "; int t; if(i == 1) t = query(now,map[i],1,n,1)-1; else if(map[i] > now) t = query(now+1,map[i],1,n,1)-1; else t = query(map[i],now,1,n,1); //cout << n - i +1-t << " " << t << "\n"; re += min(t,n - i + 1 - t); update(map[i],0,1,n,1); now = map[i]; } printf("%I64d\n",re); } return 0; }
hdu 4263 Red/Blue Spanning Tree 【】
题目大意:现有一颗blue,red染色的无向无权联通图。问能否正好有k个蓝边构成的生成树!
解法:这个题的解法比较多样,我一个同学的解法是:红边优先生成树和蓝边优先生成树,如果这两种生成树的蓝边分别<=和>=k,那么存在所求生成树。
解法二:http://blog.acmj1991.com/?p=1355并查集解法
解法三:我用的就是解法三,将蓝边构成的图求连通分量,那么每个联通分量中最多有点个数-1个边在生成树中,求所有的边的和。红边同样求,如果蓝的值>=k,红的边的最大值>=n-k-1 那么存在所求。
#include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int map[1100][1100]; bool vis[1100]; int n,m,k; int dfs(int rt,int type) { vis[rt] = 1; int re = 1; for(int i = 0;i < n;i++) { if(map[rt][i] == type && !vis[i]) re += dfs(i,type); } return re; } int main() { while(scanf("%d%d%d",&n,&m,&k) != EOF) { if(n == 0 && m == 0 && k == 0) break; memset(map,0,sizeof(map)); char s[2]; int a,b; for(int i = 0;i < m;i++) { scanf("%s%d%d",s,&a,&b); int t; if(s[0] == 'B') t = 1;else t = 2; map[a-1][b-1] = t; map[b-1][a-1] = t; } int blue = 0; memset(vis,0,sizeof(vis)); for(int i = 0;i < n;i++) if(!vis[i]) blue += dfs(i,1)-1; int red = 0; memset(vis,0,sizeof(vis)); for(int i = 0;i < n;i++) if(!vis[i]) red += dfs(i,2)-1; //cout << blue << " " << red; int re; if(blue >= k && red >= n-k-1) re = 1;else re = 0; printf("%d\n",re); } return 0; }