CodeForce#190 Div1

Problem A Ciel and Robot (数学)

题意:机器人初始在(0,0),反复执行一个移动序列,问是否能够抵达(a,b)
题解:找出机器人第一轮的所能到达的所有位置,然后根据最后一步到达与初始位置的偏移量(dx,dy)作为位移,看它第一轮到的所有位置(sx,sy)是否能找到一个非负整数n,满足sx+n*dx=a且sy+n*dy=b
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dr[][2]=
{
    0,1,
    0,-1,
    -1,0,
    1,0
};
int MP[300];
int stk[105][2];
bool check(int sx,int sy,int dx,int dy,int ex,int ey)
{
    int x=ex-sx,y=ey-sy;
    if(dx==0)
    {
        if(sx!=ex)return false;
        if(dy==0)return sy==ey;
        y=ey-sy;
        if(y/dy>=0&&y%dy==0)return true;
        else return false;
    }
    else
    {
        if(x/dx>=0&&x%dx==0)
        {
            if(dy==0)return sy==ey;
            return y%dy==0&&y/dy==x/dx;
        }
        else
            return false;
    }
}
int main()
{
    //freopen("data.in","r",stdin);
    MP['U']=0;
    MP['D']=1;
    MP['L']=2;
    MP['R']=3;
    int a,b;
    while(scanf("%d%d",&a,&b)!=EOF)
    {
        char s[105];
        scanf("%s",s);
        int top=1,flag=0;
        stk[0][0]=stk[0][1]=0;
        for(int i=0; s[i]; i++)
        {
            stk[top][0]=stk[top-1][0]+dr[MP[s[i]]][0];
            stk[top][1]=stk[top-1][1]+dr[MP[s[i]]][1];
            top++;
        }
        int dx=stk[top-1][0],dy=stk[top-1][1];
        for(int i=0; i<top&&!flag; i++)
        {
            if(check(stk[i][0],stk[i][1],dx,dy,a,b))
                flag=1;
        }
        printf("%s\n",flag?"Yes":"No");
    }
    return 0;
}

Problem B Ciel and Duel (贪心)

题意;两人打牌,牌有两种,攻和守,打牌的人也是分为攻和守,攻方手上全是攻,守方两者皆可以有,每张牌也有自己的权值。攻方可以指定守方出某一张牌,然后他按照规则打出一张牌,规则就是:如果选出的牌是守,那么攻方打出的牌的权值必须比守牌大,且这一回合他不能得分;如果选出的牌是攻,那么攻方打出的牌必须不小于这张牌,得分为他打出的牌的权值减去另一张牌权值;如果守方没有牌了,那么攻方剩余的牌全部成为他的得分。问最高得分。
题解:分两种情况讨论,一、将守方牌耗光,贪心策略为用用最小能比守方守牌大的牌去耗掉那张守牌,守牌耗光了在用最小大于对方攻牌的牌去耗掉对方的牌;二、不将牌耗光,那么只需要与对面攻牌打就行了,因为对付守牌又没得分还浪费牌,贪心策略是用最大的攻击牌打对方最小的攻击牌,原因如下:
记:a1,a2,b1,b2分别为两方攻击牌,如果a1>a2>b1>b2,那么无论怎么安排,得到结果一样;如果a1>b1>a2>b2,按照大对小,得分为a1-b2,按照大对大,就是a1-b1+a2-b2=a1-b2+a2-b1,然而a2-b1<0,所以不如大对小好。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=105;
int atk[N],atk2[N],def2[N];
bool mark[N];
int main()
{
    int n,len;
    while(scanf("%d%d",&n,&len)!=EOF)
    {
        int lena=0,lend=0,i,j;
        for(i=0;i<n;i++){
            char s[5];
            int v;
            scanf(" %s%d",s,&v);
            if(s[0]=='A')atk2[lena++]=v;
            else def2[lend++]=v;
        }
        for(int i=0;i<len;i++)scanf("%d",&atk[i]);
        sort(atk,atk+len);
        sort(atk2,atk2+lena);
        sort(def2,def2+lend);
        memset(mark,false,sizeof(mark));
        int ans1=0,ans2=0;
        for(i=0,j=0;i<lend&&j<len;i++)
        {
            while(j<len&&(mark[j]||atk[j]<=def2[i]))j++;
            if(j==len)break;
            mark[j]=true;
        }
        if(j<len)
        {
            for(i=0,j=0;i<lena&&j<len;i++)
            {
                while(j<len&&(mark[j]||atk[j]<atk2[i]))j++;
                if(j==len)break;
                mark[j]=true;
                ans1+=atk[j]-atk2[i];
            }
            if(j<len){
                for(i=0;i<len;i++)
                    if(!mark[i])ans1+=atk[i];
            }
            else
                ans1=0;
        }
        for(i=len-1,j=0;i>=0&&j<lena&&atk[i]>=atk2[j];i--,j++)
        {
            ans2+=atk[i]-atk2[j];
        }
        printf("%d\n",max(ans1,ans2));
    }
    return 0;
}

Problem C Ciel and Commander (树的分治)

题意:给你一颗数,含n个结点(n<10^5),每个结点分配一个人,人的级别从‘A’~‘Z’依次递减,求一个可行的分配方案使得任意两个相同级别的人之间都会有至少一个比他们级别高的人在。
题解:开始用最长链的方式构造,结果在样例5挂掉了,少考虑种情况。所以,这题正解还是树的分治,选择树的重心(至于为什么选重心,可以有两个理由,一是重心比较好,使得每个孩子的结点数能够尽可能的平摊,二是,树也没有其他什么心了,排除了所有的不可能,也就只能选它了=。=)标号为当前可用最大级别,然后递归解决它的子树,递归的时候让子树可选的最大标号为当前根的下一级别。


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=100005;
bool mark[N];
int head[N],size[N],nc;
char rank[N];
int n;
struct edge
{
    int to,next;
} edge[N*3];
void add(int a,int b)
{
    edge[nc].to=b;
    edge[nc].next=head[a];
    head[a]=nc++;
    edge[nc].to=a;
    edge[nc].next=head[b];
    head[b]=nc++;
}
void getsize(int x,int fa)//以x为根的子树的结点数目
{
    size[x]=1;
    for(int i=head[x]; i!=-1; i=edge[i].next)
    {
        int t=edge[i].to;
        if(mark[t]||t==fa)
            continue;
        getsize(t,x);
        size[x]+=size[t];
    }
}
int bestroot(int x)//找树的重心
{
    getsize(x,x);
    int half=size[x]>>1;
    while(1)
    {
        int id=x,mmax=0;
        for(int i=head[x]; i!=-1; i=edge[i].next)
        {
            int t=edge[i].to;
            if(!mark[t]&&mmax<size[t])
                mmax=size[id=t];
        }
        if(mmax<=half)
            break;
        size[x]-=size[id];
        size[id]+=size[x];
        x=id;
    }
    return x;
}
void dfs(int root,char ra)
{
    root=bestroot(root);
    rank[root]=ra;
    mark[root]=true;
    for(int i=head[root];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(!mark[to])
            dfs(to,ra+1);
    }
}
int main()
{
    //freopen("data.in","r",stdin);
    while(scanf("%d",&n)!=EOF)
    {
        memset(mark,false,sizeof(mark));
        memset(head,-1,sizeof(head));
        nc=0;
        for(int a,b,i=0; i<n-1; i++)
        {
            scanf("%d%d",&a,&b);
            add(a,b);
        }
        dfs(1,'A');
        for(int i=1;i<=n;i++)
            printf("%c%c",rank[i],(i<n)?' ':'\n');
    }
    return 0;
}

Problem D Ciel and Flipboard (数学+枚举)

题意:n*n的矩阵(n为奇数)上每个格子都有一个整数,现在可以执行一个操作,就是选取x*x的矩形区域把里面所有的整数取反,x=(n+1)/2,操作可以反复执行,问最后n*n矩阵的所有元素之和的最大值是多少。
题解:一个很难想的一道题,首先,需要知道最关键的两个等式。
记f[i][j]为位置a[i][j]最后翻转情况,取值为-1或者1
由于x恰好等于n的一半多一点,也就是如果只考虑行,每一行的第x列肯定会被翻转,而且第j列与第j+x列肯定不能被同时翻转,但定会有一个被翻(这个可以画图理解)
行的话也是这样,这也就可以得到两个等式
f[i][j]*f[i][x]*f[i][j+x]=1
f[i][j]*f[x][j]*f[i+x][j]=1
这就是说确定了任意两个都可以唯一确定第三个
通过枚举第x列的前x行的f情况,复杂度为2^17,然后以这几行可以唯一确定第x列的剩下的行。
然后,对与每一种情况进行运算,方法就是单独的枚举第x行的前x-1列(在前x-1列,每一个元素的取值与另外的元素无关,就是说他们可以自由分配自己取多少,原因可以参考线性代数,他们实际上是线性无关的)。
当x行j列确定后,利用x行x列得到x行j+x的值,然后第j列与第j+x列的最大值可以得出来。(指定一个f[i][j],利用f[x][j]可以得到f[x+i][j],向右也可以得到f[i][j+x],f[i+x][j+x],这四个是绑定的,一个改了另三个就跟着改,可以得到两个值,且互为相反数,取绝对值即可)
对于f[x][j]的两种取值可以得到j列与j+x列的最大值,然后取最大即可。
由于互相之间都不影响,所以枚举复杂度也就是2*(x-1)*(计算列的复杂度)。
PS:代码里面矩阵标号用的是0~n-1,所以会有些许差异,但是思想就是这样的。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=50;
int n,m,a[N][N],f[N][N];
int getval(int j)
{
    int ans=0;
    for(int i=0;i<m;i++)
        ans+=abs(a[i][j]+f[i][m]*a[i][j+m+1]+f[m][j]*a[i+m+1][j]+f[i][m]*f[m][j+m+1]*a[i+m+1][j+m+1]);
    return ans;
}
int solve(int mask)
{
    //printf("mask=%d ",mask);
    int ans=0;
    for(int i=0;i<=m;i++)
    {
        f[i][m]=(mask&(1<<i))?-1:1;
        ans+=a[i][m]*f[i][m];
    }
    for(int i=m+1;i<n;i++)
    {
        f[i][m]=f[i-m-1][m]*f[m][m];
        ans+=a[i][m]*f[i][m];
    }
    for(int j=0;j<m;j++)
    {
        int ta;
        f[m][j]=-1;
        f[m][j+m+1]=f[m][j]*f[m][m];
        ta=getval(j)+f[m][j]*a[m][j]+f[m][j+m+1]*a[m][j+m+1];
        f[m][j]=1;
        f[m][j+m+1]=f[m][j]*f[m][m];
        ta=max(ta,getval(j)+f[m][j]*a[m][j]+f[m][j+m+1]*a[m][j+m+1]);
        ans+=ta;
    }
    //printf("ans=%d\n",ans);
    return ans;
}
int main()
{
    //freopen("data.in","r",stdin);
    while(scanf("%d",&n)!=EOF)
    {
        int ans=-10000000;
        m=n>>1;
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                scanf("%d",&a[i][j]);
        for(int mask=0,msize=1<<(m+1);mask<msize;mask++)
            ans=max(ans,solve(mask));
        printf("%d\n",ans);
    }
    return 0;
}


Problem E Ciel and Gondolas (DP优化)

题意:n个人分成m组上船(次序必须一致),每个人都有互相的不熟悉程度,在一艘船上互相不认识的程度之和就是这艘船的不熟悉度,然后所有船的不熟悉度之和就是总的,然后求总的不熟悉度的最小值。
题解:
很容易可以想到dp[i][j]=min(dp[k][j-1]+cost[k+1][i])
由不熟悉矩阵就可以看出,他们是满足四边形不等式优化的。
然后就是上面的dp了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=4004,M=805;
int unfam[N][N];
int cost[N][N];
int dp[N][M],s[N][M];
int row[N][N];
int n,m;
void GetCost()
{
    for(int i=1; i<=n; i++)
    {
        row[i][0]=0;
        for(int j=1; j<=n; j++)
        {
            row[i][j]=row[i][j-1]+unfam[i][j];
        }
    }
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            if(i>=j)
                cost[i][j]=0;
            else
                cost[i][j]=cost[i][j-1]+row[j][j]-row[j][i-1];
        }
    }
}
void Solve()
{
    memset(dp,0x3f,sizeof(dp));
    int i,j,k;
    for (i=1; i<=n; i++)
    {
        dp[i][1]=cost[1][i];
        s[i][1]=1;
    }
    for (j=2; j<=m; j++)
    {
        s[n+1][j]=n;
        for (i=n; i>=j; i--)
        {
            for (k=s[i][j-1]; k<=s[i+1][j]; k++)
                if (dp[i][j]>dp[k][j-1]+cost[k+1][i])
                {
                    dp[i][j]=dp[k][j-1]+cost[k+1][i];
                    s[i][j]=k;
                }
        }
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
                scanf("%d",&unfam[i][j]);
        GetCost();
        Solve();
        printf("%d\n",dp[n][m]);
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值