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

题目: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_back
struct 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;
			else	parent[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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值