2021上海站(重温经典)

  • 导语
  • 涉及的知识点
  • 题目
  • D
  • E
  • G
  • I
  • K
  • 参考文献

导语

拿来练手的,好长时间没弄了,生疏了不少,思维和分析需要再加强

涉及的知识点

思维,数学,背包DP,树形DP,Kruscal重构树,位运算,构造,二分图

链接:第 46 届 ICPC 国际大学生程序设计竞赛亚洲区域赛(上海)

题目

D

题目大意:给出一个分数QProcess案例 qp案例分析大赛2021_QProcess案例,现在找到两个正整数QProcess案例 qp案例分析大赛2021_#include_02使得等式QProcess案例 qp案例分析大赛2021_QProcess案例_03,如果无解就输出两个0

思路:将原问题转换,设QProcess案例 qp案例分析大赛2021_#include_04,那么原等式就可以变成QProcess案例 qp案例分析大赛2021_概率论_05,等价于求解方程QProcess案例 qp案例分析大赛2021_#include_06,可得解为QProcess案例 qp案例分析大赛2021_概率论_07,可知题目条件是有理数解,那么QProcess案例 qp案例分析大赛2021_QProcess案例_08就必须是有理数,得出来的解分别对应QProcess案例 qp案例分析大赛2021_算法_09,因此直接对给定的QProcess案例 qp案例分析大赛2021_QProcess案例_10带入方程求出解,判断是否是有理数解即可,如果是,则进一步将求得的解化为分数形式获得QProcess案例 qp案例分析大赛2021_#include_02

代码

#include <bits/stdc++.h>
#include <iostream>
#include <cstring>
#include <stdlib.h>
#include <stdio.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e5+10;
const int mod = 1e9+7;
ll gcd(ll a,ll b) {
    if(a<b)
        swap(a,b);
    ll yu;
    yu=a%b;
    while(true) {
        a=b;
        b=yu;
        if(yu==0)
            break;
        yu=a%b;
    }
    return a;
}
void solve() {
    bool bol=true;
    ll p,q;
    scanf("%lld%lld",&p,&q);
    ll k=p*p-4*q*q;
    ll kk=sqrt(k);
    if(kk*kk==k)
        bol=true;
    else
        bol=false;
    ll gcdd=gcd(p-kk,q*2);
    if(!bol)
        printf("0 0\n");
    else {
        printf("%lld %lld\n",(p-kk)/gcdd,q*2/gcdd);
    }
}
int main() {
    int t = 1;
    scanf("%d",&t);
    while(t--)
        solve();
    return 0;
}

E

题目大意:给出n个整数和一个参数k,现在要从n个整数里挑选m个数,使得这m个数两两之间差的绝对值大于等于k,求m的最大值

思路:直接排序,然后贪心的选取数字即可,选取的时候使用二分

代码

#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int N = 1e5+5;
int a[N];
void solve()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i = 1;i <= n;i++)
    {
        scanf("%d",a+i);
    }
    sort(a+1,a+n+1);
    int ans = 0;
    int t = a[1];
    int pos = 1;
    while(pos!=n+1)
    {
        t=a[pos];
        t+=k;
        ans++;
        pos = lower_bound(a+1,a+1+n,t)-a;
    }
    printf("%d\n",ans);
}
int main()
{
	int t = 1;
	while(t--)
		solve();
    return 0;
}

G

题目大意:给出一个n点n-1边的无向连通图,n必为奇数,现在要将n-1条边分成(n-1)/2组,每组满足的条件如下:一组只有两条边, 两条边有一个共同点,输出所有满足条件的分组方案的方案数对998244353取模后结果

思路:对于以QProcess案例 qp案例分析大赛2021_QProcess案例_12为根的树核它的一个子节点QProcess案例 qp案例分析大赛2021_动态规划_13,如果QProcess案例 qp案例分析大赛2021_动态规划_13子树可匹配边为偶数(即自己的子树边就可以两两分组),那么QProcess案例 qp案例分析大赛2021_概率论_15这条边就可以被QProcess案例 qp案例分析大赛2021_QProcess案例_12拿来使用,如果为奇数,显然QProcess案例 qp案例分析大赛2021_概率论_15这条边就不能使用了

QProcess案例 qp案例分析大赛2021_算法_18

对于这样一棵简单的树来说,假设可用边为2n个,那么最后得到的方案s数为QProcess案例 qp案例分析大赛2021_概率论_19,化简可得QProcess案例 qp案例分析大赛2021_#include_20,这便是简单的一层的方案数,那么对于第n层的节点u来说(从下到上),设QProcess案例 qp案例分析大赛2021_概率论_21为子节点QProcess案例 qp案例分析大赛2021_#include_22方案数,目前的总方案数为:QProcess案例 qp案例分析大赛2021_动态规划_23,以此类推即可(2n为可用边数)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=2e5+10;
const int mod=998244353;
struct node {
    int nt,to;
} e[maxn];
ll head[maxn],cnt,n,dp[maxn];
void Add(int from,int to) {//链式前向星
    e[++cnt].nt=head[from];
    e[cnt].to=to;
    head[from]=cnt;
}
bool DFS(int u,int f) {//深搜,树上dp
    dp[u]=1;
    ll ans=0;
    for(int i=head[u]; i; i=e[i].nt) {
        int v=e[i].to;
        if(v==f)continue;
        if(!DFS(v,u))ans++;//统计有多少条可用边
        dp[u]=dp[u]*dp[v]%mod;//乘法原理,直接排列组合
    }
    for(int i=1; i<=ans; i+=2)dp[u]=dp[u]*i%mod;//带入公式算总方案
    return ans&1;
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>n;
    for(int i=0; i<n-1; i++) {//建树
        int u,v;
        cin >>u>>v;
        Add(u,v);
        Add(v,u);
    }
    DFS(1,1);//搜索
    cout <<dp[1]<<endl;
    return 0;
}

I

题目大意:给出n张牌,每张牌有一个数字标签和一个价值,可以至多选择s张牌翻倍数字标签,现在从加倍之后的牌中选择两组牌,使得牌的数字标签之和相等且价值和最大

思路:根据数据范围来看,第一个想法就是dp,分析题目,感觉是三维dp,因为至少有两个参数,一个是牌的编号,另一个是第几个翻倍的,然而这两个参数肯定不够用,因为题目还要分组,如果暂时忽略分组的条件,可以发现原问题变成了经典的类似01背包问题,那么只需要来找一个量来约束分组即可,可以使用两组的数字标签和之差来进行约束,即QProcess案例 qp案例分析大赛2021_算法_24,因为我们的目的不是求出S和T的具体值,而是使得S和T的差值为0,因为S-T可能是负数,在实现过程中需要离散化一下

那么就可得到QProcess案例 qp案例分析大赛2021_QProcess案例_25,i为第几张牌,j为已经翻倍了几次,k为S-T的值(右移了),根据不同的情况:放入S不加倍,放入S加倍,放入T不加倍,放入T加倍直接进行状态转移即可

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,s,dp[101][101][5201],v[101],t[101];
const int inf=0x3f3f3f3f;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>n>>s;
    for(int i=1; i<=n; i++)
        cin >>v[i]>>t[i];
    for(int i=0; i<=s; i++)//初始化
        for(int j=0; j<=5200; j++)
            dp[0][i][j]=-inf*(j!=2600);
    //这个地方不能动2600,因为2600为解,一开始解为0
    for(int i=1; i<=n; i++)
        for(int j=0; j<=s; j++)
            for(int k=0; k<=5200; k++) {
                dp[i][j][k]=dp[i-1][j][k];
                if(k>=t[i])dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k-t[i]]+v[i]);
                //装入T
                if(k+t[i]<=5200)dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k+t[i]]+v[i]);
                //装入S
                if(j&&k>=2*t[i])dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k-2*t[i]]+v[i]);
                //加倍后装入S
                if(j&&k+2*t[i]<=5200)dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k+2*t[i]]+v[i]);
                //加倍后装入T
            }
    cout <<dp[n][s][2600]<<endl;
    return 0;
}

K

题目大意:构造一个01串,每一秒变换规则如下:

  1. 第i位为1,下一秒i-1和i+1为1,i为0,溢出可忽略
  2. 第i和i+2位为1,下一秒这两个1会抵消,i+1变成0
  3. 第i和i+1位为1,下一秒两个都为0

要求变换前后串中都有1,构造一个初始串满足2n秒变换中出现两个完全相同的串

思路:可以找规律发现,1001和10001这两个串是可以一直循环的,以及在这两种串后加上10(也可以在前面加一个01)也是一直循环的,那么可行解的情况无非以下几种:4a,4a+2,4a+5b,5b,4a+5b+2直接根据给定的n值进行判断即可,也可以直接判断对4取余,余数是几就拿对应构造好的字符串来消去余数,剩下的直接1001循环即可

代码

#include <bits/stdc++.h>
using namespace std;
string s[] = {"0", "0", "01", "", "1001", "10001", "011001", "0101001"};
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    int n;
    cin >>n;
    if(n==3)cout <<"Unlucky\n";
    else {
        if(n<=7)cout <<s[n]<<endl;
        else {
            int ans=0;
            if(n%4==0)ans=n/4;//4a
            else if(n%4==1) {//余数为1直接拿一个5来填
                cout <<s[5];
                ans=n/4-1;
            } else if(n%4==2) {//余数为2拿一个01填前面
                cout <<s[2];
                ans=n/4;
            } else {//余数为3拿一个010来填
                cout <<s[7];
                ans=n/4-1;
            }
            while(ans--)cout <<"1001";
            cout <<endl;
        }
    }
    return 0;
}

参考文献

  1. 2021 ICPC上海 I.Steadily Growing Steam(dp)
  2. 2021 ICPC上海 G.Edge Groups(树形dp)
  3. 2021上海区域赛
  4. 上海站题解