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

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

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

题目大意:说起来比较麻烦,就是不断的放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;
}

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

题目大意:汉诺塔 将所有东西从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;
}




你可能感兴趣的:(ACMer,套题)