这套题应该是由于每一个题目的时限都比较长,所以,被杭电用来测试系统了。也就是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
#include
#include
#include
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
#include
#include
#include
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>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
#include
#include
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
#include
#include
#include
#include
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 ma;//从小到大的 队列里出的是大的
priority_queue ,greater > 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 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
#include
#include
#include
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
#include
#include
#include
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;
}