2021上海站(重温经典)
- 导语
- 涉及的知识点
- 题目
- D
- E
- G
- I
- K
- 参考文献
导语
拿来练手的,好长时间没弄了,生疏了不少,思维和分析需要再加强
涉及的知识点
思维,数学,背包DP,树形DP,Kruscal重构树,位运算,构造,二分图
链接:第 46 届 ICPC 国际大学生程序设计竞赛亚洲区域赛(上海)
题目
D
题目大意:给出一个分数,现在找到两个正整数使得等式,如果无解就输出两个0
思路:将原问题转换,设,那么原等式就可以变成,等价于求解方程,可得解为,可知题目条件是有理数解,那么就必须是有理数,得出来的解分别对应,因此直接对给定的带入方程求出解,判断是否是有理数解即可,如果是,则进一步将求得的解化为分数形式获得
代码
#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取模后结果
思路:对于以为根的树核它的一个子节点,如果子树可匹配边为偶数(即自己的子树边就可以两两分组),那么这条边就可以被拿来使用,如果为奇数,显然这条边就不能使用了
对于这样一棵简单的树来说,假设可用边为2n个,那么最后得到的方案s数为,化简可得,这便是简单的一层的方案数,那么对于第n层的节点u来说(从下到上),设为子节点方案数,目前的总方案数为:,以此类推即可(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背包问题,那么只需要来找一个量来约束分组即可,可以使用两组的数字标签和之差来进行约束,即,因为我们的目的不是求出S和T的具体值,而是使得S和T的差值为0,因为S-T可能是负数,在实现过程中需要离散化一下
那么就可得到,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串,每一秒变换规则如下:
- 第i位为1,下一秒i-1和i+1为1,i为0,溢出可忽略
- 第i和i+2位为1,下一秒这两个1会抵消,i+1变成0
- 第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;
}
参考文献
- 2021 ICPC上海 I.Steadily Growing Steam(dp)
- 2021 ICPC上海 G.Edge Groups(树形dp)
- 2021上海区域赛
- 上海站题解