vjudge_contest15

本文记录了一场编程比赛中的五个问题解决方案。A题通过暴力解决小数据范围问题;B题利用排序和相邻元素比较寻找相反数;C题采用倒序DP解决博弈问题;D题运用线段树处理区间操作构建图并应用Dijkstra算法;E题通过二分查找解决序列分组问题。

比赛链接

比赛中解决的问题

A

Problem description

可以抽象地看做数轴上有两个点,坐标为a,c,每个点一次可以向右移动b,d,问两点最快在哪个坐标能重叠,若不能则输出-1(a,b,c,d <= 100)

Solution

作为一道A题,是一道裸暴力题,因为数据范围很小,所以可以暴力解决。
当然也要想一下,当数据范围变大时,应该如何解决,现在能想出来的方法是用扩展欧几里得来实现,应该可以过100000以内的数据,若更大就不知道应该如何更优地解决了

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int main(){
    int a,b,c,d,e,f;
    cin >> a >> b >> c >> d;
    for (int i=1;i<=100000;++i){
        if (b < d)  b += a;
        else if (b > d) d += c;
        else{
            cout << b << endl;
            return 0;
        }
    }
    cout << -1 << endl;
    return 0;
}

B

Problem description

给出一个数列,求这个数列中是否存在两个数互为相反数,每个数据中有两个数列,只要有一个满足即输出Yes,否则输出No,数列长度 <= 10000

Solution

先讲数列按照每个数的绝对值排序,这时就可以很好的利用STL中可自定义比较函数的特性,排序完后将相邻两数相加,若为0则存在两个数互为相反数

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

int s[10010];

bool cmp(int a,int b){
    return abs(a) < abs(b);
}

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for (int k=1;k<=m;++k){
        int p;
        bool bo = 1;
        cin >> p;
        for (int i=1;i<=p;++i)
            cin >> s[i];
        sort(s+1,s+p+1,cmp);
        for (int i=2;i<=p;++i)
            if (s[i] + s[i-1] == 0){
                bo = 0; 
            }
        if (bo){
            for (int i=k+1;i<=m;++i){
                cin >> p;
                for (int i=1;i<=p;++i)
                    cin >> s[i];
            }
            cout << "YES" << endl;
            return 0;
        }
    } 
    cout << "NO" << endl;
    return 0;
}

C

Problem description

一个数列,假设某处有一个物品,使用集合中的数将物品向右移动一定的格数,移到第一个格子的胜,问是必赢还是必输,若无限循环则输出“loop”。(物品数量n <= 7000)

Solution

刚看时以为是一道博弈论题,但因为数据范围比较小,而且因为这题的转移数很少,所以可以考虑使用dp的方法,dp[n][0],dp[n][1],分别表示到第n个位置是必输还是必败。考虑到这题正着dp难度很大,考虑从结果出发,倒着dp,采用BFS的方式,就可以解决这个问题了

code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;

bool flag[7010][2],dp[7010][2];
int w[7010][2],cnt[7010][2],n,k[2];

void calc(int x,int num){
    flag[x][num] = 1;
    for (int i=0;i<k[!num];++i){
        int tmp = (x+n-w[i][!num]) % n;
        if (tmp == 0)   continue;
        if (flag[tmp][!num])    continue;
        if (dp[x][num] && ++cnt[tmp][!num] == k[!num])
            calc(tmp,!num);
        else if (!dp[x][num])
            dp[tmp][!num] = 1,
            calc(tmp,!num);
    }
} 

int main(){
    memset(flag,0,sizeof flag);
    memset(dp,0,sizeof dp);
    scanf("%d",&n);
    scanf("%d",&k[0]);
    for (int i=0;i<k[0];++i)
        scanf("%d",&w[i][0]);
    scanf("%d",&k[1]);
    for (int i=0;i<k[1];++i)
        scanf("%d",&w[i][1]);
    calc(0,0);
    calc(0,1);
    for (int i=0;i<=1;++i){
        for (int j=1;j<n;++j)
            if (!flag[j][i])    cout << "Loop ";
            else if (dp[j][i])  cout << "Win ";
            else cout << "Lose ";
        cout << endl;
    }
}

比赛后解决的问题

D

Problem description

对于一张图,已知三种操作,分别为①将两点相连,边权为val[i],②将一个点和一个区间内的点相连,边权为val[i],③将一个区间内的点相连,边权为val[i],求连完后以s为起点到个点的最小消耗
点数 操作数 <= 100000

Solution

因为②③两种操作涉及区间操作,所以不能将区间中每个点都展开,连边所以考虑使用线段树来维护,建两棵线段树,一棵用来记录第②种操作,另一颗用来记录第③中操作,先预先建好单向边长度为0,在添加操作时连接底边和区间的点,连接单向边,长度为边权值。建完图后跑一个dijkstra,可以得出s到各点的距离

code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <vector>
#define int long long
const long long Inf = 1400000;
using namespace std;

long long tot = 0,dist[3000010],fst[3000010],nxt[3000010],lst[3000010],des[3000010],dis[3000010],n,u,val;
bool flag[3000010],bo[3000010];

struct xint{
    long long x,y;
}s1[3000010],s2[3000010];

struct cmp{bool operator()(xint &a,xint &b){
    return a.y > b.y;
}};

void addedge(long long a,long long b,long long val){
    if (!fst[a])    fst[a] = ++tot;
    else nxt[lst[a]] = ++ tot;
    lst[a] = tot,des[tot] = b,dis[tot] = val;
    //cout << a << " " << b << " " << val << " ! " << endl;
}

void maketree(long long l,long long r,long long cnt){ 
    s1[cnt].x = l,s1[cnt].y = r;
    s2[cnt].x = l,s2[cnt].y = r;
    if (l == r){
        addedge(cnt+n,l,0),addedge(l,cnt+n,0),addedge(l,cnt+n+Inf,0),addedge(cnt+n+Inf,l,0);
        return ;
    }
    addedge(cnt+n,cnt*2+n,0),addedge(cnt+n,cnt*2+n+1,0),addedge(cnt*2+n+Inf,cnt+n+Inf,0),addedge(cnt*2+1+n+Inf,cnt+n+Inf,0);
    maketree(l,(l+r)/2,cnt*2),maketree((l+r)/2+1,r,cnt*2+1);
}

void calc1(int l,int r,int cnt){
    if (s1[cnt].x == l && s1[cnt].y == r){
        addedge(u,cnt+n,val);
        return ;
    }
    int mid = (s1[cnt].x+s1[cnt].y) / 2;
    if (mid >= r)   calc1(l,r,cnt*2);
    else if (mid < l)   calc1(l,r,cnt*2+1);
    else calc1(l,mid,cnt*2),calc1(mid+1,r,cnt*2+1);
}

void calc2(int l,int r,int cnt){
    if (s2[cnt].x == l && s2[cnt].y == r){
        addedge(cnt+n+Inf,u,val);
        return ;
    }
    int mid = (s2[cnt].x+s2[cnt].y) / 2;
    if (mid >= r)   calc2(l,r,cnt*2);
    else if (mid < l)   calc2(l,r,cnt*2+1);
    else calc2(l,mid,cnt*2),calc2(mid+1,r,cnt*2+1);
}

priority_queue < xint,vector<xint>,cmp > sq;

main(){
    int q,s,k,lx,ry;
    scanf("%lld%lld%lld",&n,&q,&s);
    maketree(1,n,1);
    for (int i=1;i<=q;++i){
        scanf("%lld",&k);
        if (k == 1){
            scanf("%lld%lld%lld",&lx,&ry,&val);
            addedge(lx,ry,val);
        }
        if (k == 2){
            scanf("%lld%lld%lld%lld",&u,&lx,&ry,&val);
            calc1(lx,ry,1);
        }
        if (k == 3){
            scanf("%lld%lld%lld%lld",&u,&lx,&ry,&val);
            calc2(lx,ry,1);
        }
    }
    for (int i=0;i<=3000009;++i)
        dist[i] = 1e15,flag[i] = 1;
    dist[s] = 0;
    sq.push((xint){s,0});
    while (!sq.empty()){
        int t = sq.top().x,temp = fst[t];
        sq.pop();
        if (!flag[t])   continue;
        flag[t] = 0;
        while (temp){
            if (flag[des[temp]] && (dis[temp]+dist[t] < dist[des[temp]])){
                dist[des[temp]] = dis[temp] + dist[t];
                sq.push((xint){des[temp],dist[des[temp]]});
            }
            temp = nxt[temp];
        }
        //cout << t << " " << dist[t] << endl;
    }
    for (int i=1;i<=n;++i)
        if (dist[i] > 1e14) cout << -1 << " ";
        else cout << dist[i] << " ";
    return 0;
}

E

Problem description

给出一个序列,要求序列中的数不同的个数不超过k,求最小分组数,其中k为1
到n,对于每种情况分别输出一个答案,序列长度 <= 100000

Solution

考虑二分的方式,对于l和r,令l

code

    #include<bits/stdc++.h>
using namespace std;
int s[100007],vis[100007],ans[100007],n;
int get_cnt(int k){
    int res = 0,cnt = 0;
    memset(vis,-1,sizeof vis);
    for (int i=1;i<=n;++i){
        if (vis[s[i]] == res) continue;
        vis[s[i]] = res;
        ++ cnt;
        if (cnt > k){
            ++ res;
            cnt = 1;
            vis[s[i]] = res;
        }
    }
    return res+1;
}

void solve(int l,int r){
    if (l > r) return ;
    int cntl = get_cnt(l),cntr = get_cnt(r);
    if (cntl == cntr){
        for (int i=l;i<=r;++i)
            ans[i] = cntl;
        return ;
    }
    ans[l] = cntl,ans[r] = cntr;
    int mid = (l+r) / 2;
    solve(l+1,mid),solve(mid+1,r-1);
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;++i)
        scanf("%d",&s[i]);
    solve(1,n);
    for (int i=1;i<=n;++i)
        cout << ans[i] << ' '; 
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值