Codeforces Round #266 (Div. 2)(解题报告)

来源:互联网 发布:asphalt 8 windows 10 编辑:程序博客网 时间:2024/06/06 17:27

题目:Round #266 (Div. 2)

A. Cheap Travel(水题)

题意:坐地铁,每次价格是a,有一种特殊票,每张价格为b,可以坐m次。求问坐n次的最少花费。

做法一般是两种,一种是直接枚举b的张数,取最小值,简单粗暴但有效,题目数据量不大。

当然,分析一下就可以发现,如果a*m > b,那么当次数有m次时,肯定是b划算。否则就全部买a的。

而如果买了多次b的之后,剩下n%m次不够买一次b,这时再判断下a*(n%m) 跟b的大小即可得到答案。

#include<cstdio>int main(){    int n, m, a, b;    while(~scanf("%d %d %d %d", &n, &m, &a, &b)){        if(a*m > b){            int ans = (n/m)*b;            n%=m;            if(n*a <= b)    ans += n*a;            else    ans += b;            printf("%d\n", ans);        }        else{            printf("%d\n", n*a);        }    }    return 0;}

B. Wonder Room(枚举)

题意:一个房间要容纳n个人,每个人要至少占用6平方米,现在房间是a*b大小,可以对a和b分别增大,问增大后能满足条件的房间的最小面积是多少。

根据题意可知房间最小面积是6*n,如果一开始a*b就大于等于6*n,不用修改直接输出答案。

对于要修改的,我是枚举a的大小,根据a求出b的最小长度,再算面积取最小值。

但是由于n<=10^9,全部枚举会超时,而当a超过sqrt(6*n)时,其实这时候b是很小的,所以当a超过sqrt(6*n),反过来就去枚举b。

枚举量最多就是2*sqrt(6*n)。

枚举的时候注意a和b不能比原来小就可以。

#include<cstdio>#include<cstring>#include<cmath>#include<algorithm>using namespace std;typedef long long LL;int main(){    LL n, a, b, c, A, B;    while(~scanf("%I64d %I64d %I64d", &n, &a, &b)){        n*=6;        if(a*b>=n){            printf("%I64d\n%I64d %I64d\n", a*b, a, b);        }        else{            LL m = (LL)sqrt(n+0.1);            LL ans = 0x7fffffffffffffffLL;            for(LL i=a; i<=m && ans>n; i++){                if(n%i==0)  c = n/i;                else    c = n/i + 1;                if(c>=b){                    if(i*c<ans){                        ans = i*c;                        A = i;                        B = c;                    }                }            }            for(LL i=b; i<=m && ans>n; i++){                if(n%i==0)  c = n/i;                else    c = n/i + 1;                if(c>=a){                    if(i*c<ans){                        ans = i*c;                        A = c;                        B = i;                    }                }            }            printf("%I64d\n%I64d %I64d\n", ans, A, B);        }    }    return 0;}

C. Number of Ways(前缀和,扫描)

题意:给定N个数的序列,对于2<=i<=j<=n-1,求出有多少组(i,j)满足sum[1, i-1],sum[i,j], sum[j+1, n]三段的和都相等。

因为三段加起来就是原来所有数的和,所以如果原先的和S不能被3整除,答案就是0,否则每段的和都应该是S/3。

我的做法是先从后往前叠加,我们先找个f[i],如果sum[i, n]等于S/3,那么标记f[i]为1,否则为0。

而cnt[i] = cnt[i+1] + f[i]。

然后再从前往后叠加,遇到sum[1, i]等于S/3的,就把cnt[i+2]加到答案。此时相当于到i为止作为第一段,然后在i+2到n里面寻找第三段的开始,剩下的就是第二段。

后来看了别人的代码,其实一次扫就可以,从左往右如果遇到sum[1, i]等于S/3的,就记录cnt[i] = cnt[i-1]+1,否则cnt[i]=cnt[i-1],遇到sum[1,i]等于S/3*2的,就把cnt[i-2]加进答案。思想还是一样的。

我的代码还是按照第一种做法来,即两边扫。

#include<cstdio>#include<cstring>typedef long long LL;const int N = 500010;int n;LL a[N];int cnt[N];int main(){    while(~scanf("%d", &n)){        LL sum = 0;        memset(cnt, 0, sizeof(cnt));        for(int i=1; i<=n; i++){            scanf("%I64d", a+i);            sum += a[i];        }        if(sum%3!=0){            puts("0");            continue;        }        LL x = sum/3;        LL y = 0;        for(int i=n; i>0; i--){            y += a[i];            cnt[i] = cnt[i+1];            if(y==x)    cnt[i]++;        }        LL ans = 0;        sum = 0;        for(int i=1; i<=n; i++){            sum += a[i];            if(sum==x){                ans += cnt[i+2];            }        }        printf("%I64d\n", ans);    }    return 0;}

D. Increase Sequence(dp)

题意:题目描述一种对序列的操作,对于给定的[l, r],一次操作表示这个区间的值全部加上1。可以有多次操作,唯一的限制是对于任意的[li, ri]和[lj, rj],li!=lj, ri!=rj。

问的是有多少种方案使得所有数字都变成给定的h。两种方案只要有一个操作区间不同,就算不同方案,当然区间的操作顺序是无关的。

首先还是把明显不可达的情况去掉,如果序列当中已经有数字大于h了,肯定没有方案,输出0。

接下来就是dp的时候了。

dp[i][j]表示到第i个数字为止,1~i所有数字都变成h,并且此时还有j个区间覆盖到,即还要往后面作用。

那么,对于a[i],计算d=h-a[i],说明要有d个区间覆盖到a[i],有两种状态转移过来:

一种是直接前面的dp[i-1][d]过来,a[i]这里不增加新区间。

另一种当然就是前面的dp[i-1][d-1]过来,a[i]再增加新区间。

所以dp[i] = dp[i-1][d] + dp[i-1][d-1]。

然后,由于我们可以选择在a[i]这里结束一个区间,有d个区间可以选择,所以还有dp[i][d-1] = dp[i][d] * d。

最后的答案就是dp[n][0],表示所有数字都变成h,而且操作的区间都结束掉了。

初始状态dp[0][0]=1,其它清0。

#include<cstdio>#include<cstring>typedef long long LL;const int mod = 1000000007;int n, h, a[2001];LL dp[2001][2000];int main(){    while(~scanf("%d %d", &n, &h)){        bool flag = 0;        for(int i=1; i<=n; i++){            scanf("%d", a+i);            if(a[i]>h)  flag = 1;        }        if(flag){            puts("0");            continue;        }        memset(dp, 0, sizeof(dp));        dp[0][0] = 1;        for(int i=1; i<=n; i++){            int d = h-a[i];            dp[i][d] = dp[i-1][d];            if(d){                dp[i][d] = (dp[i][d] + dp[i-1][d-1])%mod;                dp[i][d-1] = dp[i][d] * d % mod;            }        }        printf("%I64d\n", dp[n][0]);    }    return 0;}

E. Information Graph(并查集+lca+离线处理)

题意:n个员工,一开始没有上下关系。然后m次询问,询问分三种:

1 x y,y成为x的上司,题目保证在此之前x没有上司。

2 x,给x一份文档,他签名,然后给他的上司签名,上司签完再给上司的上司,直到没有上司了。

3 x i,询问x是否给编号i的文档签过名。文档编号是按照询问2出现的顺序从1开始标的。

昨晚做的时候考虑欠缺,system test挂掉了。

由于上下关系只有添加没有删减,所以我们可以先把所有询问读进来,顺便把上下关系构建起来。这种关系构成的可能是树,也可能是森林,我们可以增加一个虚拟节点0,让那些还没上司的当0为上司,这样就转化成树了。

完成之后我们才正式来处理m个询问。

那么,对于一份由x开始签名的文档,如果y签过名,y肯定在x向根走的路径上,y必须是x的祖先。所以用lca(y, x)是否等于y来做这个判断。

但是这里有一个问题,如果y是x签过某份文档之后,再成为x的上司,y是不会签到这份文档的。处理也很简单,增加个并查集,判断下他们在文档签完名是否已经在同一组即可。

另外,由于文档签名了之后信息是不会改变的,所以每处理一个询问2,就顺便把对应该文档的询问3处理掉,这样也不用担心y在x签完文档之后才成为x的上司的问题。

最后再按询问的顺序把询问3输出。

#include<cstdio>#include<cstring>#include<vector>#include<algorithm>using namespace std;const int LOG = 20;const int N = 100010;#define pb push_backstruct Query{    int id, x;    Query(){}    Query(int id, int x):id(id),x(x){}};vector<Query> Q[N];vector<int> V[N];inline void in(int &x){    x=0;    char c=getchar();    while(c<48 || c>57) c=getchar();    while(c>=48 && c<=57){        x = x*10+c-48;        c = getchar();    }}int n, m, q;int t[N], x[N], y[N], ans[N], f[N];int find(int X){    int Y = X;    for(; X!=f[X]; X=f[X]);    return f[Y]=X;}bool mk[N];int parent[LOG][N];int depth[N];void dfs(int x, int p, int d){parent[0][x] = p;depth[x] = d;for(int i=0; i<V[x].size(); i++){if(V[x][i] != p)dfs(V[x][i], x, d+1);}}void init(){    for(int i=1; i<=n; i++){        if(mk[i]){            V[0].pb(i);        }    }    dfs(0, -1, 0);    for(int k=0; k+1<LOG; k++){for(int i=0; i<=n; i++){if(parent[k][i]<0)parent[k+1][i] = -1;elseparent[k+1][i] = parent[k][parent[k][i]];}}}int lca(int u, int v){if(depth[u] > depth[v]) swap(u, v);for(int k=0; k<LOG; k++){if((depth[v]-depth[u]) >> k&1){v = parent[k][v];}}if(u==v)return u;for(int k=LOG-1; k>=0; k--){if(parent[k][u] != parent[k][v]){u = parent[k][u];v = parent[k][v];}}return parent[0][u];}int main(){    in(n); in(m);    q = 0;    int c = 0;    for(int i=1; i<=n; i++) mk[i] = 1;    for(int i=0; i<m; i++){        in(t[i]); in(x[i]);        if(t[i]!=2) in(y[i]);        else    y[i]=++c;        if(t[i]==1){            V[y[i]].pb(x[i]);            mk[x[i]] = 0;        }        else if(t[i]==3){            Q[y[i]].pb(Query(++q, x[i]));        }    }    for(int i=1; i<=n; i++){        f[i]=i;    }    init();    for(int i=0; i<m; i++){        if(t[i]==1){            f[x[i]] = y[i];        }        else if(t[i]==2){            int p = find(x[i]);            for(int j=0; j<Q[y[i]].size(); j++){                Query &qu = Q[y[i]][j];                int k = find(qu.x);                if(k!=p || lca(qu.x, x[i])!=qu.x){                    ans[qu.id] = 0;                }                else{                    ans[qu.id] = 1;                }            }        }    }    for(int i=1; i<=q; i++) puts(ans[i]?"YES":"NO");    return 0;}


0 0
原创粉丝点击