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;
}
<think>我们需要检查amp_contest_five_star_person_detail表中的POLICY_CD是否都存在于amp_ac_policy_info表中,关联条件为: amp_contest_five_star_person_detail.AG = amp_ac_policy_info.eaag 和 amp_contest_five_star_person_detail.POLICY_CD = amp_ac_policy_info.polnum 我们可以使用LEFT JOIN或者NOT EXISTS来找出那些在amp_contest_five_star_person_detail表中有记录,但是在amp_ac_policy_info表中没有匹配的记录。 这里提供两种方法: 方法1:使用LEFT JOIN 思路:将amp_contest_five_star_person_detail表左连接amp_ac_policy_info表,然后找出那些连接后amp_ac_policy_info表中相关字段为NULL的记录。 方法2:使用NOT EXISTS 思路:使用子查询,检查在amp_contest_five_star_person_detail表中是否存在记录,其AG和POLICY_CD在amp_ac_policy_info表中没有对应的eaag和polnum。 我们通常更倾向于使用NOT EXISTS,因为它在遇到第一个匹配项时就会停止,可能更高效,尤其是在数据量大的情况下。 但是,我们也可以使用LEFT JOIN并检查右表的主键是否为NULL。 下面分别给出两种方法的SQL语句: 方法1:LEFT JOIN ```sql SELECT d.* FROM amp_contest_five_star_person_detail d LEFT JOIN amp_ac_policy_info p ON d.AG = p.eaag AND d.POLICY_CD = p.polnum WHERE p.eaag IS NULL; -- 或者p.polnum IS NULL,因为这两个条件同时成立 ``` 方法2:NOT EXISTS ```sql SELECT d.* FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ); ``` 以上两个查询都会返回amp_contest_five_star_person_detail表中那些在amp_ac_policy_info表中没有对应保单的记录。 如果我们只是想知道是否存在这样的记录(即是否所有POLICY_CD都存在),我们可以使用以下查询,如果返回记录数为0,则表示都存在。 例如,使用NOT EXISTS的方式统计: ```sql SELECT COUNT(*) AS missing_count FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ); ``` 如果missing_count为0,则说明所有记录都存在。 解释: 1. 在LEFT JOIN方法中,我们通过左连接,将两个表按照AG=eaag和POLICY_CD=polnum进行连接。如果右表中没有匹配的行,则右表的所有列都会是NULL。然后我们通过WHERE p.eaag IS NULL来过滤出这些没有匹配的记录。 2. 在NOT EXISTS方法中,子查询用于检查是否存在匹配的记录。如果子查询没有返回任何行,则NOT EXISTS条件为真,该行就会被选中。 注意:在LEFT JOIN中,我们使用了p.eaag IS NULL作为条件,这是因为eaag在关联条件中,如果连接失败,它必然为NULL。同样,我们也可以使用p.polnum IS NULL,效果一样。 建议:为了性能,确保在关联字段上有索引,即amp_ac_policy_info表的eaag和polnum字段,以及amp_contest_five_star_person_detail表的AG和POLICY_CD字段。 如果我们需要检查某个特定的AG和POLICY_CD是否存在,也可以单独查询,但这里我们需要检查整个表。 现在,我们给出一个完整的示例,假设我们只需要知道缺失的数量,我们可以执行: ```sql SELECT COUNT(*) AS missing_count FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ); ``` 如果missing_count大于0,则说明有记录不存在于amp_ac_policy_info表中。 但是,问题要求查询的是“是否都在”,所以我们可以直接判断这个计数是否为0,如果为0则都在,否则不都在。 因此,我们可以这样写一个查询来返回布尔值(在MySQL中,我们可以使用CASE语句返回一个可读的结果): ```sql SELECT CASE WHEN EXISTS ( SELECT 1 FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ) ) THEN '存在缺失的保单' ELSE '所有保单都存在' END AS check_result; ``` 或者,我们也可以直接返回缺失的记录,让调用者自己判断。 根据需求,我们可能需要不同的输出。这里我们给出一个返回缺失记录列表的查询,以及一个返回布尔结果的查询。 如果我们需要一个简单的布尔值(在MySQL中,没有布尔类型,我们可以返回0或1): ```sql SELECT IF(COUNT(*) > 0, 0, 1) AS all_exist FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ); ``` 这个查询返回0表示有缺失,1表示全部存在。 但是,注意:如果表很大,这个COUNT(*)可能会比较慢,因为我们不需要知道具体数量,只需要知道是否存在缺失,所以我们可以用LIMIT 1来优化: ```sql SELECT IF( EXISTS ( SELECT 1 FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ) LIMIT 1 ), 0, 1) AS all_exist; ``` 或者更简洁地,直接利用EXISTS子查询: ```sql SELECT NOT EXISTS ( SELECT 1 FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ) ) AS all_exist; ``` 这里,最外层的NOT EXISTS:如果存在至少一条在amp_ac_policy_info表中没有匹配的记录,则整个结果为0(false),否则为1(true)。注意:这个查询返回1表示所有记录都存在,0表示存在缺失。 但是,注意:最外层的NOT EXISTS会使得当内层查询有结果时返回0,没有结果时返回1。而内层查询是查找缺失的记录,所以如果内层查询返回了记录(即存在缺失),那么最外层的NOT EXISTS就会是false(0);如果内层查询没有返回记录(即没有缺失),那么最外层的NOT EXISTS就是true(1)。所以这个逻辑是正确的。 因此,我们可以使用这个查询来直接返回一个布尔值(在MySQL中,用1和0表示true和false)。 总结:根据你的需求选择不同的查询语句。 如果只是想查看哪些记录缺失,使用: ```sql SELECT d.* FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ); ``` 如果想返回一个布尔值表示是否全部存在,使用: ```sql SELECT NOT EXISTS ( SELECT 1 FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ) ) AS all_exist; ``` 这样,如果all_exist为1,则说明所有POLICY_CD都在amp_ac_policy_info表里存在;为0则说明存在缺失。 注意:在大型表上,确保关联字段有索引,否则查询可能会很慢。 最后,根据问题,我们可能需要执行的是检查,所以这里我给出检查缺失记录的查询,因为这样更直观,可以看到哪些记录缺失。 但是,如果问题只是要求一个是否都在的布尔结果,那么第二个查询更合适。 由于问题描述是“查询amp_contest_five_star_person_detail表的POLICY_CD是否都在amp_ac_policy_info表里存在”,所以我们可以直接返回布尔结果。 因此,最终选择返回布尔结果的查询。 但是,如果我们需要在程序中使用,我们可以这样写: ```sql SELECT NOT EXISTS ( SELECT 1 FROM amp_contest_five_star_person_detail d LEFT JOIN amp_ac_policy_info p ON d.AG = p.eaag AND d.POLICY_CD = p.polnum WHERE p.eaag IS NULL ) AS all_exist; ``` 这个查询使用了LEFT JOIN的方式,然后通过WHERE p.eaag IS NULL来找出缺失的记录,然后最外层的NOT EXISTS来判断是否存在这样的记录。如果存在缺失,则子查询有结果,NOT EXISTS返回0;如果没有缺失,则子查询无结果,NOT EXISTS返回1。 两种方式都可以,性能上可能会有差异,取决于数据分布和索引情况。 我们选择一种即可,这里选择使用NOT EXISTS子查询的方式,因为避免了LEFT JOIN可能产生的临时表。 所以,最终代码: ```sql SELECT NOT EXISTS ( SELECT 1 FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ) ) AS all_exist; ``` 这个查询将返回一个名为all_exist的列,值为1表示全部存在,0表示存在缺失。 现在,我们可以在MySQL客户端运行这个查询。 注意:如果两个表很大,请确保在关联字段上建立了合适的索引。 例如,在amp_ac_policy_info表上,建议有(eaag, polnum)的复合索引,在amp_contest_five_star_person_detail表上,建议有(AG, POLICY_CD)的复合索引,这样查询才能高效运行。 如果没有索引,查询可能会很慢。 因此,在运行之前,最好检查索引情况。 创建索引的语句(如果还没有索引的话): ```sql -- 为amp_ac_policy_info表创建索引 CREATE INDEX idx_amp_ac_policy_info_eaag_polnum ON amp_ac_policy_info(eaag, polnum); -- 为amp_contest_five_star_person_detail表创建索引 CREATE INDEX idx_amp_contest_detail_ag_policycd ON amp_contest_five_star_person_detail(AG, POLICY_CD); ``` 但是,请注意,创建索引可能会影响写操作的性能,并且需要一定的存储空间,所以请根据实际情况决定。 最后,根据问题,我们给出了解决方案。</think>要检查 `amp_contest_five_star_person_detail` 表的 `POLICY_CD` 是否都存在于 `amp_ac_policy_info` 表中(通过 `AG` 和 `POLICY_CD` 分别关联 `eaag` 和 `polnum`),可以使用以下 SQL 查询: ### 方法 1:使用 LEFT JOIN 查找缺失数据(推荐) ```sql SELECT d.* FROM amp_contest_five_star_person_detail d LEFT JOIN amp_ac_policy_info p ON d.AG = p.eaag AND d.POLICY_CD = p.polnum WHERE p.eaag IS NULL; -- 右表关键字段为 NULL 表示无匹配 ``` ### 方法 2:使用 NOT EXISTS 子查询 ```sql SELECT d.* FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ); ``` ### 方法 3:检查是否存在缺失数据(布尔结果) ```sql SELECT NOT EXISTS ( SELECT 1 FROM amp_contest_five_star_person_detail d WHERE NOT EXISTS ( SELECT 1 FROM amp_ac_policy_info p WHERE d.AG = p.eaag AND d.POLICY_CD = p.polnum ) ) AS all_exist; -- 返回 1(全存在)或 0(有缺失) ``` --- ### 关键解释: 1. **关联逻辑**: - `d.AG = p.eaag` 确保代理机构代码匹配 - `d.POLICY_CD = p.polnum` 确保保单号匹配 2. **性能优化**: - 在 `amp_ac_policy_info(eaag, polnum)` 和 `amp_contest_five_star_person_detail(AG, POLICY_CD)` 上创建复合索引 - 使用 `EXISTS` 通常比 `JOIN` 更高效(找到一条匹配即终止扫描) 3. 结果说明: - 方法 1 和 2 返回缺失的原始数据记录 - 方法 3 直接返回布尔值(1 表示全存在,0 表示有缺失) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值