2021 JiangXi Provincinal Collegiate Programing Contest
文章目录
- 2021 JiangXi Provincinal Collegiate Programing Contest
- [K. Many Littles Make a Mickle](https://codeforces.com/gym/103366/problem/K)
- [B. Continued Fraction](https://codeforces.com/gym/103366/problem/B)
- [L. It Rains Again](https://codeforces.com/gym/103366/problem/L)
- [A. Mio visits ACGN Exhibition](https://codeforces.com/gym/103366/problem/A)
- [H. Hearthstone So Easy](https://codeforces.com/gym/103366/problem/H)
K. Many Littles Make a Mickle
- 题意
有n层楼,第i层楼有i*i个房间,每个房间可以容纳m个人,问n层楼总共可以容纳多少人
-
题解
水题
-
代码
#include <iostream>
using namespace std;
int main() {
int t;
cin>>t;
while(t--) {
long long n,m;
cin>>n>>m;
long long res=0;
for(int i=1;i<=n;i++) res+=i*i;
res*=m;
cout<<res<<'\n';
}
return 0;
}
B. Continued Fraction
-
题意
给一个分数x/y,把它化成一下形式,输出n,以及a[0~n]
-
题解
原式 = a 0 + 1 . . . a 0 = x y 1 . . . = y x m o d y . . . 作为新的原式继续计算 a 1 . . . a n 原式=a_0+\frac{1}{...}\\ a_0=\frac{x}{y}\\ \frac{1}{...}=\frac{y}{x\bmod y}\\ ...作为新的原式继续计算a_1...a_n 原式=a0+...1a0=yx...1=xmodyy...作为新的原式继续计算a1...an
由上述的计算过程,可以发现每次都在用x%y和y来递归计算,和gcd一样,直接套用gcd,同时需要记录a数组大小及其值 -
代码
#include <iostream>
using namespace std;
const int N=110;
int a[N],cnt;//分别记录a的值,数组a的大小
void gcd(int x,int y) {
if(y==0) return ;
a[cnt++]=x/y;
gcd(y,x%y);
}
int main() {
int t;
cin>>t;
while(t--) {
int x,y;
cin>>x>>y;
cnt=0;//每次记得大小置为0
gcd(x,y);
cout<<cnt-1<<" ";//输出数组的最后下标n,所以-1
for(int i=0;i<cnt;i++)cout<<a[i]<<" ";
cout<<'\n';
}
return 0;
}
L. It Rains Again
-
题意
给定一些(x1,y2)到(x2,y2)的挡雨板,问最后不被淋到的地方有多少米
-
题解
可以看到纵坐标完全没用,直接简化成一位坐标就行,可以把题目抽象成这样一个模型:每给一个板子就把x1~x2区间内的加上一个板子,最后统计所有点开头的长度为1的区间不被淋的有几个;即对很多个个开区间[x1,x2)操作,然后看所有板数大于0点的总和,但直接对区间操作时间复杂度过高,可以转变为差分
-
代码
#include <iostream>
using namespace std;
const int N=1e5+5;
int b[N];//差分数组
int res;
int main() {
int n;
cin>>n;
for(int i=1;i<=n;i++) {
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
b[x1]++,b[x2]--;//开区间的差分操作
}
for(int i=1;i<=100000;i++) {
b[i]+=b[i-1];//充当和数组
if(b[i]>0) res++;
}
cout<<res<<'\n';
return 0;
}
A. Mio visits ACGN Exhibition
- 题意
给n*m的只含有01的地图,问从(1,1)->(n,m)的不回头所有路径中至少经过p个0,q个1的路径数量
-
题解
一眼dp
定义:如下,但是爆空间了
f [ i , j , x , y ] : 从 ( 1 , 1 ) 到 ( i , j ) 所有路径有 x 个 0 , y 个 1 的数量 f[i,j,x,y]:从(1,1)到(i,j)所有路径有x个0,y个1的数量 f[i,j,x,y]:从(1,1)到(i,j)所有路径有x个0,y个1的数量
定义优化:从(1,1)到(n,m)步数固定,所以01个数和固定i+j-1==x+y
f [ i , j , x ] : 从 ( 1 , 1 ) 到 ( i , j ) 所有路径有 x 个 0 , i + j − 1 − x 个 1 的数量 f[i,j,x]:从(1,1)到(i,j)所有路径有x个0,i+j-1-x个1的数量 f[i,j,x]:从(1,1)到(i,j)所有路径有x个0,i+j−1−x个1的数量
计算:
f [ i , j , x ] = { f [ i − 1 , j , x ] + f [ i , j − 1 , x ] g [ i , j ] = 1 f [ i − 1 , j , x − 1 ] + f [ i , j − 1 , x − 1 ] g [ i , j ] = 0 , x > 0 f[i,j,x]= \begin{cases} f[i-1,j,x]+f[i,j-1,x] &g[i,j]=1\\ f[i-1,j,x-1]+f[i,j-1,x-1] &g[i,j]=0,x>0 \end{cases} f[i,j,x]={f[i−1,j,x]+f[i,j−1,x]f[i−1,j,x−1]+f[i,j−1,x−1]g[i,j]=1g[i,j]=0,x>0
虽然还是爆空间,但比可以发现只用到i和i-1这两层,所以可以用滚动数组优化 -
坑
- 忘记初始化,以及初始化搞错定义
- 初始点需要continue,防止被覆盖
- k需要倒着遍历,脑子里想一下这个三维遍历图,会发现k=i+j-1时,f[j,k]一定是前一层没有遍历过得到的值,而f[j,k]需要用到上一层及上一行的旧的f值,所以从后往前遍历合理;如果从前往后遍历会出现这样的情况:所有的f[j,1]会由f[j,0]=0更新并且往后覆盖,导致所有f[j,k]=0
- g[i,j]=0&&k=0时,也需要更新值f[j,k]
- 代码
#include <iostream>
using namespace std;
const int N=510,M=10010,mod=998244353;//题目要求取模
int n,m,p,q;
int g[N][N];
int f[N][M];
int main() {
scanf("%d%d%d%d",&n,&m,&p,&q);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&g[i][j]);
f[1][!g[1][1]]=1;//按定义初始化
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) {
if(i==1&&j==1)continue;//如果没有这个,初始化的值1会被覆盖为0
for(int k=i+j-1;k>=0;k--)
if(g[i][j]) f[j][k]=(f[j][k]+f[j-1][k])%mod;
else {
//可以转移的前提是k>0
if(k) f[j][k]=(f[j][k-1]+f[j-1][k-1])%mod;
else f[j][k]=0;//k=0时也需要更新,因为此时g[i,j]=0
} //说明路径上一定有一个0,故f[j,0]=0
}
int res=0;//把所有到了(n,m)的点且个数符合的所有路径数量和
for(int i=p;i<=n+m-q-1;i++) res=(res+f[m][i])%mod;
printf("%d\n",res);
return 0;
}
-
补充
dp定义不变,但是集合划分即状态转移方程换一种方式
H. Hearthstone So Easy
-
题意
p与f玩一个游戏,游戏规则为:初始两人都有n点血,中毒值为0;游戏有四个阶段,p抽卡,p选择奶自己还是扣f血量k,f抽卡,f选择奶自己还是扣p血量k;抽卡阶段会扣中毒值血量,同时中毒值+1;当睡的血量<=0时游戏结束。问p,f谁赢
-
题解(定性+定量)
-
可以发现,只要某一回合两人活着,那么结束时两人的血量一样
-
分析先手如何能取胜:
- 先手劣势是一定会先在抽卡阶段受到伤害,相当于此时相对血量最少,可能会在这时被扣死
- 所以先手需要尽量在上一轮没被抽卡扣死时把后手杀死
- 故:先手要一直选择用卡杀后手
-
分析后手如何能取胜:
- 后手不可能用卡杀死先手,因为如果后手能用卡,就说明后手在中毒+被先手扣的k血量不能致死,那么同样的如果后手用卡扣先手,对于先手也是中毒+被后手扣的k血量,并不能让先手死。
- 所以后手只能维持一样血量的同时拖死先手,让先手被毒死
-
考虑先手在第m轮扣死后手,同时后手使用回奶维持同血量的
{ n − m ( m − 1 ) 2 − k − m > 0 , 先手要在 m − 1 轮被打 + m 轮抽卡不被杀 n − m ( m + 1 ) 2 − k < = 0 , 后手没来得及在 m 轮回奶维持原样就死 \begin{cases} n-\frac{m(m-1)}{2}-k-m>0 &,先手要在m-1轮被打+m轮抽卡不被杀\\ n-\frac{m(m+1)}{2}-k<=0 &,后手没来得及在m轮回奶维持原样就死\\ \end{cases} {n−2m(m−1)−k−m>0n−2m(m+1)−k<=0,先手要在m−1轮被打+m轮抽卡不被杀,后手没来得及在m轮回奶维持原样就死
很显然上式矛盾,故先手除了在第一轮杀死后手就不可能必胜
-
-
代码
#include <iostream>
using namespace std;
int main()
{
int t;
cin>>t;
while(t--){
int n,k;
cin>>n>>k;
//特判先手直接被扣死
if(n==1){cout<<"freesin"<<'\n';continue;}
//如果先手能在第一轮杀死后手,先手胜
if(n<=k+1)cout<<"pllj"<<'\n';
//否则后手必胜
else cout<<"freesin"<<'\n';
}
return 0;
}