The University of Chicago Invitational Programming Contest 2012 解题报告

来源:互联网 发布:电商大数据 电子书 编辑:程序博客网 时间:2024/05/16 06:34

这套题应该是由于每一个题目的时限都比较长,所以,被杭电用来测试系统了。也就是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 1000010int 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;}

hdu 4359 Double Dealing 【模拟+循环节+最小公倍数】

题目大意:说起来比较麻烦,就是不断的放card然后再按顺序取起来,重复操作。问最少几次可以使得回到原来的顺序。

解法:首先模拟一次的操作,这样的话,就相当于产生了一次置换,然后求这个置换的循环节,所有循环节长度的最小公倍数就是答案。

这个题目比较简单,但是25秒的时限我居然超时了!!最后查到的是,memset 了一个二维数组,而其实这个二维数组只需要第二位=0的情况!靠

code;

#include <iostream>#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;#define N 810int 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;}

hdu 4260 The End of The World 【递归理解】

题目大意:汉诺塔 将所有东西从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 2020int 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 1000010int 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)>>1int 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;}///pppppppppppppppint 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;}