暑期训练 CF套题

CodeForces 327A

题意:有n个数,都是0或1,然后必须执行一次操作,翻转一个区间,里面的数0变1,1变0,求最多1的数量

思路:最开始我写的最大字段和,后面好像写搓了,然后我又改成暴力,因为这个范围只有100,写n^3都没事,所以我们第一层枚举左区间,第二层枚举右区间,然后我们第三层记录左边1的数量,中间的0的数量,右边的1的数量即可

 

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 100005
#define mod 1000000007
using namespace std;
typedef long long ll;
ll a[1005],num,n;
int main(){
    scanf("%lld",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    ll mx=0;
    for(int i=1;i<=n;i++){
        for(int j=i;j<=n;j++){
            ll shu=0;
            for(int k=1;k<i;k++){
                if(a[k]) shu++;
             }
             for(int k=i;k<=j;k++){
                if(a[k]==0) shu++;
             }
             for(int k=j+1;k<=n;k++){
                if(a[k]) shu++;
             }
             mx=max(shu,mx);
        }
    }
    printf("%lld",mx);
} 
View Code

 

 

CodeForces 854C

题意:有n架飞机,现在因为一些原因飞机都只能拖延到k+1时刻后出发,一个时刻只能出发一架,每架飞机有个晚飞一分钟损失多少,最开始飞机是按顺序安排的时刻表,现在问你怎么排表损失最小

思路:首先我们知道最开始是 1 2 3 4 5,如果k为2,那么我们只能是 3 4 5 6 7 ,但是我们要怎么安排呢,我们能清晰的知道,每个人都是一分钟损耗多少,所以我们肯定是让最大的尽量少损失,因为差值都是固定的  3 4 5 6 7 - 1 2 3 4 5  差值和为10  ,只是我们现在要排列把这个差值给哪些数,我们肯定把差值尽量给损失小的,所以我们排个序,我们找最接近当前数的值,但是我们找又是个问题,一个只能用一次,用完就要删除,而且我们不能遍历找,符合这些要求的有个很好的东西就是 set,我们可以利用log级别的删除,set.lower_bound log级别的查找就能很好解决这个事

 

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#define maxn 300005
#define mod 1000000007
using namespace std;
typedef long long ll;
struct sss
{
    ll id;
    ll x;
}a[maxn];
ll n,m;
ll vis[maxn];
set<ll> q;
int cmp(struct sss x,struct sss y){
    return x.x>y.x;
}
int main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++){
        q.insert(i+m);
    }
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i].x);
        a[i].id=i;
    }
    sort(a+1,a+n+1,cmp);
    ll sum=0;
    for(int i=1;i<=n;i++){
        ll x=*q.lower_bound(a[i].id);
        //set<ll>::iterator t1=q.upper_bound(a[i].x);    
        sum+=(x-a[i].id)*a[i].x;
        vis[a[i].id]=x;
        q.erase(x);
    }
    printf("%lld\n",sum);
    for(int i=1;i<=n;i++){
        printf("%lld ",vis[i]);
    }
} 
View Code

 

 

CodeForces 863C

题意:有两个人玩石头剪刀布,最开始两个人会选择出什么,然后两个人都会根据对方和自己现在出的东西而改变下次出的,然后会进行k轮游戏,输出每个人赢得次数

思路:这个k特别大,但是情况最多有9种,也就是说9种过后必定会出现重复的,这个时候说明有循环节,我们把中间周期除掉计算即可

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#define maxn 300005
#define mod 1000000007
using namespace std;
typedef long long ll;
struct sss
{
    ll sum1,sum2;
}c[maxn];//记录当前步的比赛状况
ll A[4][4];
ll B[4][4]; 
ll vis[4][4];
ll chuan(ll a,ll b){
    if(a==b) return 0;
    if(a==1&&b==2) return -1;
    if(a==1&&b==3) return 1;
    if(a==2&&b==3) return -1;
    if(a==2&&b==1) return 1;
    if(a==3&&b==2) return 1;
    if(a==3&&b==1) return -1;
}
int main(){
    ll k,a,b;
    scanf("%lld%lld%lld",&k,&a,&b);
    for(int i=1;i<=3;i++){
        for(int j=1;j<=3;j++){
            scanf("%lld",&A[i][j]);
            vis[i][j]=-1;
        }
    }
    for(int i=1;i<=3;i++){
        for(int j=1;j<=3;j++){
            scanf("%lld",&B[i][j]);
        }
    }
    ll sum1=0,sum2=0;
    ll z=0,flag=0;
    while(z<k){
        ll w=chuan(a,b);
        if(w==1) sum1++;
        else if(w==-1) sum2++;
        if(vis[a][b]!=-1){//找到循环节
            ll len=z-vis[a][b];
            ll sy=k-z-1;
            sum1+=sy/len*(sum1-c[vis[a][b]].sum1);
            sum2+=sy/len*(sum2-c[vis[a][b]].sum2);
            sy%=len;
            k=sy;
            flag=1;
            ll a1=a,b1=b;
            a=A[a1][b1];
            b=B[a1][b1];
            break;
        }
        c[z].sum1=sum1;
        c[z].sum2=sum2;
        vis[a][b]=z;
        ll a1=a,b1=b;
        a=A[a1][b1];
        b=B[a1][b1];
        z++;
    }
    z=0;
    while(flag&&z<k){
        ll w=chuan(a,b);
        if(w==1) sum1++;
        else if(w==-1) sum2++;
        ll a1=a,b1=b;
        a=A[a1][b1];
        b=B[a1][b1];
        z++;
    }
    printf("%lld %lld",sum1,sum2);
} 
View Code

 

 

CodeForces 749D

题意:最开始有n个人,每个人会选择一个团队,自身带一个价值,然后下面有q次查询,说明这次比拼哪些团队不会参加,然后选获胜团队以及获胜团队里面最低能战胜其他所有人的人的价值

思路:我们首先用vector记录所以人的值,这肯定要做的,然后我们会发现我们要战胜肯定是要把其他所有人的最大值战胜,所以我们用一个set记录所有团队的最大值,我们缺席的话直接暴力

在set里面删除,然后我们再找出当前最大,那就是获胜者,然后找出次大,然后在获胜者团队二分找出正好比他大的值即可

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<set>
#include<utility>
#include<algorithm>
using namespace std;
#define mp make_pair
#define X first
#define Y second
const int N=200008;
int n,m,q,a[N],b[N];
int ban[N],c[N];
set<int>d[N];
pair<int,int>e[N];
int main(void)
{
    int i,p1,p2;
    scanf("%d",&n);
    for(i=1;i<=n;i++){scanf("%d%d",&p1,&p2);b[p1]=p2;d[p1].insert(p2);}
    for(i=1;i<=n;i++)e[i]=mp(b[i],i);
    sort(e+1,e+1+n);
    scanf("%d",&q);
    while(q--)
    {
        scanf("%d",&m);
        for(i=1;i<=m;i++){scanf("%d",c+i);ban[c[i]]=1;}
        p1=p2=0;
        for(i=n;i>=1;i--)
        {
            if(0==e[i].X)break;
            if(ban[e[i].Y])continue;
            if(p1){p2=e[i].Y;break;}
            else p1=e[i].Y;
        }
        if(p1==0)printf("0 0\n");
        else if(p2==0)printf("%d %d\n",p1,*d[p1].begin());
        else printf("%d %d\n",p1,*d[p1].lower_bound(*(--d[p2].end())));
        for(i=1;i<=m;i++)ban[c[i]]=0;
    }
    return 0;
}
View Code

 

 

CodeForces 926E

题意:开始有 n个数,然后如果有相同的连续的数x我们要合并,合并之后之前两个数删除然后插入x+1,然后直到不能再合并,下标是左边那个的下标,输出最后结果

思路:我们会发现合并必须要从低到高执行,因为新插入的是x+1,所以我们必须从低到高,然后我们要考虑的问题是,合并后,但是我的数组其实还是一样的位置,比如 1 1 2,我合并第 1 2个数数组里面就会变成  2 0 2 ,0代表这个位置没数了,但是我们下次合并又要怎么知道1 3个数可以合并呢,暴力肯定不现实,这个时候我们又会发现其实每个数也就和他的下个数有关,我们想用删除一个数,又要查询两个数是否相连,有个很适合这个结构,那就是链表,我们清晰的记录下一个下标是谁,然后上面我们合并后又要插入进去,那肯定是使用优先队列了,然后我们相等的值按下标排序,如果当前两个数不匹配,那么说前面这个数的位置肯定确定下来了。因为要么是下标不符合,中间隔了数,肯定不能再合并。要么是值不相等。从低到高,已经没别的合并的机会了

#include<cstdio>
#include<cmath>
#include<queue>
#include<iostream>
#include<algorithm> 
#define maxn 200005
#define mod 1000000007
using namespace std;
typedef long long ll;
struct sss{
    ll id;
    ll next;
    friend bool operator <(struct sss x,struct sss y){
        if(x.next==y.next){
            return x.id>y.id;
        }
        return x.next>y.next;
    }
}a[maxn];
ll n;
priority_queue<struct sss> q;
ll b[maxn],c[maxn];
int main(){
    ll n;
    cin>>n;
    for(int i=1;i<=n;i++){
        a[i].id=i;
        if(i!=n)
            a[i].next=i+1;
        else a[i].next=-1;
    }
    for(int i=1;i<=n;i++){
        cin>>b[i];
        struct sss e;
        e.id=i;
        e.next=b[i];
        q.push(e);
    }
    ll num=0;
    while(q.size()>=2){
        struct sss x,y;
        x=q.top();q.pop();
        y=q.top();q.pop();
        if(a[x.id].next==y.id&&x.next==y.next){
            x.next++;
            a[x.id].next=a[y.id].next;
            q.push(x);
            num++;
        }
        else{
            c[x.id]=x.next;
            q.push(y);
        }
    }
    if(!q.empty()){
        struct sss x=q.top();q.pop();c[x.id]=x.next;
    }
    if(!q.empty()){
        struct sss x=q.top();q.pop();c[x.id]=x.next;
    }
    cout<<n-num<<"\n"; 
    for(int i=1;i<=n;i++){
        if(c[i]!=0){
            cout<<c[i]<<" ";
        }
    }
} 
View Code

 

 

CodeForces 455C

题意:给定N,M和Q,N表示有N个城市,M条已经修好的路,修好的路是不能改变的,然后是Q次操作,操作分为两种,一种是查询城市x所在的联通集合中,最长的路为多长。二是连接两个联通集合,采用联通之后最长路最短的方案,无环图。

思路:因为一开时的图是不可以改变的,所以一开始用dfs处理出各个联通集合,并且记录住最大值,然后就是Q次操作,用并查集维护,注意因为联通的时候要采用最长路径最短的方案,所以s的转移方程变为s = max(s, (s+1)/2 + (s0+1)/2 + 1),因为两个连通块都记录了树的直径,如果我们其中一条路径是 1-2-3-4-5-6-7,那么这个连通块我肯定选择4点连接,为什么呢,因为如果我选3,那么我就可以走3-4-5-6-7,很明显长度更大,那么最优的肯定是选择中间点,所以我们每次合并的时候就是 选择两个路径的中间点长度再加上新加的那条求一个max

 

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
 
using namespace std;
const int maxn = 3 * 1e5 + 5;
int N, M, Q, f[maxn], s[maxn];
int root, ans, rec;
vector<int> g[maxn];
 
int getfar(int x) {
    return f[x] == x ? x : f[x] = getfar(f[x]);
}
 
void link (int u, int v) {
    int x = getfar(u);
    int y = getfar(v);
 
    if (x == y)
        return;
 
    if (s[x] < s[y])
        swap(s[x], s[y]);
 
    f[y] = x;
    s[x] = max(s[x], (s[x] + 1) / 2 + (s[y] + 1) / 2 + 1);
}
 
void dfs (int u, int p, int d) {
    f[u] = root;
 
    if (d > ans) {
        ans = d;
        rec = u;
    }
 
    for (int i = 0; i < g[u].size(); i++) {
        if (g[u][i] != p)
            dfs(g[u][i], u, d+1);
    }
}
 
int main () {
    int type, u, v;
 
    scanf("%d%d%d", &N, &M, &Q);
    for (int i = 1; i <= N; i++) {
        f[i] = i;
        g[i].clear();
    }
 
    for (int i = 0; i < M; i++) {
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
 
    for (int i = 1; i <= N; i++) {
        if (f[i] == i) {
            root = rec = i;
            ans = -1;
            dfs(i, 0, 0);
 
            ans = -1;
            dfs(rec, 0, 0);
            s[i] = ans;
        }
    }
 
    for (int i = 0; i < Q; i++) {
        scanf("%d", &type);
        if (type == 1) {
            scanf("%d", &u);
            v = getfar(u);
            printf("%d\n", s[v]);
        } else {
            scanf("%d%d", &u, &v);
            link(u, v);
        }
    }
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/Lis-/p/11330620.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值