UPC 2020年夏混合个人训练第六十六场【C&D&E&F】

问题 C:箱子


题目描述
小猪佩奇和其他n-1个小伙伴在玩一个老套的游戏。
一个房间中,有n个随机打乱过的箱子放成一排,每个箱子里有一张纸条,写着一个人的名字。每个人要按一定顺序走进房间,打开最多k个箱子,如果其中没有自己的名字游戏就失败了。每个人走出房间的时候需要关上箱子。(游戏中箱子的顺序不会再被调换),游戏前他们可以商量出一个策略,但是游戏开始之后他们不能互相交流。
小猪佩奇想知道最优策略下游戏成功,即每个人都找到写有自己名字的箱子的概率。

输入
第一行一个整数T
接下来T行每行两个正整数n,k

输出
T行,每一行游戏成功的概率 mod 998244353

样例输入
1
2 1

样例输出
499122177

提示
解释:
每个人只有一次开箱机会,我们把房间内的箱子随意编号为1或2,让第一个人打开1号箱子,第二个人打开2号箱子,游戏成功的概率是1/2,可以证明这个是最优的。

数据范围:
对于10%的数据,n,k<=3
对于另外20%的数据,k=1
对于另外20%的数据,n是偶数,k=n/2
对于60%的数据,n<=1000
对于另外20%的数据,n<=100000, T=2
对于100%的数据,1<=k<=n<=200000, 1<=T<=50.

题解:

考虑策略: 第i个人策略是这样的,打开i,如果a[i]!=i,打开a[i],如果a[a[i]]!=i,打开a[a[i]]……依次类推。
那么显然成功的概率就是排列形成的每个环大小都不超过k。

证明这是最优策略: 考虑一个简单问题,每个人进去开了箱子就不闭上了。显然新问题获胜概率大于等于原问题。
在这个简单问题里,每个箱子都要开一次。每个人进去只会开没开的箱子,这些箱子不管哪个都是一样的,因此相当于随机开箱。

对于一种开箱方法,即一个人如果开出了数字s1~st(t<=k),我们可以构造p[s1]=s2,p[s2]=s3……p[st]=s1。那么每种开箱方法对应了一个排列,这个排列形成的每个环大小都不超过k。

因此新问题的获胜概率就是排列形成的每个环大小都不超过k。这样我们也就证明了原问题的这个策略最优。

然后DP就很简单了。

// C. 一般动规与递推+博弈论
#include <bits/stdc++.h>
#define ll long long 
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=200010 ,mo=998244353;

int ni[maxn],p[maxn];
int i,j,k,l,t,n,m,ca,ans;

int qsm(int x,int y)
{
    if(!y)  return 1;
    
    int t = qsm(x,y/2);
    t = (ll)t*t %mo;
    
    if(y%2)  t = (ll)t*x %mo;
    return t;
}

int main()
{
    fo(i,1,200000) ni[i]=qsm(i,mo-2);
    scanf("%d",&ca);
    
    while(ca--)
	{
        scanf("%d%d",&n,&k);
        p[0]=1;
        
        fo(i,1,n)
		{
            p[i]=p[i-1];
            if(i-k-1>=0)  (p[i]-=p[i-k-1]) %= mo;
            p[i] = (ll)p[i]*ni[i] %mo;
            (p[i]+=p[i-1]) %= mo;
        }
        
        ans = (p[n]-p[n-1]) %mo;
        (ans+=mo) %= mo;
        
        printf("%d\n",ans);
    }
}

问题 D:老师的任务 poly


题目描述
福州时代中学的老师都十分优秀,他们工作起来非常认真刻苦。为了使自己的工作更加轻松一点(老师也有偷懒的时候),我们尊敬的数学老师想实现一个功能:只需输入一个多项式的项数和各项的系数,就能直接输出这个多项式。这个功能将会给老师带来极大的便利。老师找到了优秀的你,让你来帮忙完成这个任务。

一元 n 次多项式可用如下的表达式表示:

其中,aixi 称为 i 次项,ai 称为 i 次项的系数。给出一个一元多项式各项的次数和系数,请按照如下规定的格式要求输出该多项式:
1.多项式中自变量为 x,从左到右按照次数递减顺序给出多项式。
2.多项式中只包含系数不为 0 的项。
3.如果多项式 n 次项系数为正,则多项式开头不出现 + 号,如果多项式 n 次项系数为负,则多项式以 - 号开头。
4.对于不是最高次的项,以 + 号或者 - 号连接此项与前一项,分别表示此项系数为正或者系数为负。紧跟一个正整数,表示此项系数的绝对值(如果一个高于 0 次的项,其系数的绝对值为 1,则无需输出 1)。如果 x 的指数大于 1,则接下来紧跟的指数部分的形式为 x^b,其中 b 为 x 的指数;如果 x 的指数为 1,则接下来紧跟的指数部分形式为 x;如果 x 的指数为 0,则仅需输出系数即可。
5.多项式中,多项式的开头、结尾不含多余的空格。

输入
输入共两行,第一行 1 个整数 n,表示一元多项式的次数。
第二行为 n+1 个整数,其中第 n-i+1 个整数表示第 次项的系数,每两个整数之间用空格隔开。

输出
输出共一行,按题目所述格式输出多项式。

样例输入
【样例1】
5
100 -1 1 -3 0 10
【样例2】
3
-50 0 0 1
样例输出
【样例1】
100x^5 -x^4+ x^3 -3x^2+10
【样例2】
-50x^3+1

提示
对于 100% 数据,0≤n≤100,-100≤系数≤100

题解:

简单的模拟,根据题目描述,用 if-else 语句分类讨论。

// D.模拟(if-else语句分类讨论) 
#include <bits/stdc++.h>
using namespace std;
 
int main() 
{
	 int n,a[1005];
	 cin>>n;
	 for(int t=0; t<n; t++)	 scanf("%d",&a[t]);
	 cin>>a[n];
	 
	 for(int t=0; t<n; t++)
	 {
		 if(t==0) // 第一个数 
		 {
			 if(a[t]!=0) // 为 0则无输出 
			 {
			 	// 为 1时无需输出,-1时单独一个负号 
				if(abs(a[t])!=1)	cout<<a[t];
				else if(a[t]==-1) 	cout<<"-";
				
				if(n-t>1)	cout<<"x^"<<n-t; // n-t表示指数,t从 0开始的好处 
				else	cout<<"x"; // 指数为 1,不表示 
			 }
		 } 
		 
		 else if(a[t]>0 && t!=0) // 非最高次幂系数为正的情况 
		 {
			if(a[t]==1) 	cout<<"+";
			else  	cout<<"+"<<a[t];
			
			if(n-t>1)	cout<<"x^"<<n-t;
			else	cout<<"x";
		 } 
		 
		 else if(a[t]<0 && t!=0) // 非最高次幂系数为负的情况 
		 {
			if(a[t]==-1) 	cout<<"-";
			else 	cout<<a[t];
			
			if(n-t>1)	cout<<"x^"<<n-t;
			else	cout<<"x";
		 }
	 }
	 
	 // 最后的常数 
	 if(a[n]>0)   cout<<"+"<<a[n]<<endl;
	 if(a[n]<0)   cout<<a[n]<<endl;
	 return 0;
}

问题 E:寻找考卷 exam


题目描述
福州时代中学的校园可大了,校园里有各式各样的建筑。你作为一个在校园里生活了一年(或者更久)的人,仍然没有走遍校园的各个角落。
今天,你闲着无聊在校园内散步,找到了一栋楼,里面藏着所有的考卷。你只需要走到这栋楼的顶层并且输入正确的密码,就能够进入顶楼的房间,得到你梦寐以求的考卷。
你非常想试一试。你历尽千辛万苦找到这栋楼,楼的大门口竖着一个木板,上面写有几个大字:「寻找考卷说明书」。
说明书的内容如下:大楼共有n+1层,最上面一层是顶层,顶层的房间里面藏着考卷。除了顶层外,大楼另有n层,每层m个房间,这m个房间围成一圈并按逆时针方向依次编号为0,…,m-1。
其中一些房间有通往上一层的楼梯,每层楼的楼梯设计可能不同。每个房间里有一个指示牌,指示牌上有一个数字x,表示从这个房间开始按逆时针方向选择第x个有楼梯的房间(假定该房间的编号为k),从该房间上楼,上楼后到达上一层的k号房间。比如当前房间的指示牌上写着2,则按逆时针方向开始尝试,找到第2个有楼梯的房间,从该房间上楼。如果当前房间本身就有楼梯通向上层,该房间作为第一个有楼梯的房间。
说明书的最后用红色大号字体写着:「寻找考卷须知:帮助你找到每层上楼房间的指示牌上的数字(即每层第一个进入的房间内指示牌上的数字)总和为打开顶楼房间门的密码」。请算出这个打开顶楼房间门的密码。

输入
第一行2个整数n和m,之间用一个空格隔开。n表示除了顶层外大楼共n层楼,m表示除顶层外每层楼有m个房间。接下来n×m行,每行两个整数,之间用一个空格隔开,每行描述一个房间内的情况,其中第(i-1)×m+j行表示第i层j-1号房间的情况(1≤i≤n,1≤j≤m)。第一个整数表示该房间是否有楼梯通往上一层(0表示没有,1表示有),第二个整数表示指示牌上的数字。注意,从j号房间的楼梯爬到上一层到达的房间一定也是j号房间。
最后一行,一个整数,表示小明从大楼底层的几号房间进入开始寻宝(注:房间编号从0开始)。

输出
一个整数,表示打开顶楼房间门的密码,这个数可能会很大,请输出对 20123 取模的结果即可。

样例输入
2 3
1 2
0 3
1 4
0 1
1 5
1 2
1

样例输出
5

提示
对于 50% 数据,有 0<N≤1000,0<x≤10^4;
对于 100% 数据,有 0<N≤1000,0<M≤10,0<x≤10^6。

题解:

模拟小明上楼的过程。
根据题目的描述藏宝楼是一个圆柱状的通天塔型建筑(多半是中空的那种),楼梯可以想象成是梯子。只要不断模拟小明来到新房间->看到牌子上要他转的次数(并往总数里加上这个数)->根据牌子指示绕着楼走够这个次数的带梯子的房间->转到目的地->上楼->来到新房间的过程就可以得出结果。

代码中优化了一下小明转圈的过程(不优化的话会超时),优化过程在代码中打注释说明。

// E. 模拟
#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n,m;//除藏宝楼外n层楼,每层楼m个房间 
	cin>>n>>m;               //(楼层和房间编号皆是从0开始)
	int lou[n][m],shu[n][m];//lou[i][j]代表第i层j号房间有无楼梯
                                //shu[i][j]代表这个房间牌子上的数字
	int b[n];               //b[i]代表第i层的有楼梯房间个数
	memset(b,0,sizeof(b));
	int now,sum=0;          //now代表小明在编号为now的房间中
                                //sum代表指示过小明的牌子上的数字的和
	for(int i=0;i<n;i++)
	for(int j=0;j<m;j++)
	{
		cin>>lou[i][j]>>shu[i][j];
		if(lou[i][j]==1)   //对每层有楼梯的房间数量进行统计
		b[i]++;
	}
	cin>>now;
	int nowceng=0;  //代表小明现在所处楼层
	while(nowceng<n) //模拟每一层的情况
	{
		int ci=0;  //统计小明已经走过多少个带楼梯的房间
		sum+=shu[nowceng][now]%20123; //有可能会有一个残忍的大数,所以先取一次模
		int need=shu[nowceng][now]%b[nowceng];//用牌子上指示的数字取 本层带楼梯房间
                                                      //数量  的模,得出的数字和牌子上的数字
                                                      //等效。这么做可以优化效率
		if(need==0) //如果取模结果为0,小明所处房间没有楼梯时会出现错误。这种结果下需要
                            //走过的带楼梯房间数和这层楼带楼梯房间的数量等效
		need=b[nowceng]; 
		while(1)
		{
			if(ci==need) //到达本层目标房间
			break;
			if(lou[nowceng][now]==1&&ci!=need-1)//到达有楼梯房间的情况
			{                                   //在到达目标房间前
				ci++;                       //ci和now都要+1
	                        if(now==m-1)//到达编号最后的房间时转回0号房间
				now=0;
				else
				now++;
			}
			else if(lou[nowceng][now]==1&&ci==need-1)//到达目标房间时,只把ci+1
		        ci++;    
			else if(lou[nowceng][now]==0)//到达无楼梯房间时,只把now+1
			if(now==m-1)
				now=0;
				else
				now++;
		}
		nowceng++;   //本层结束时把nowceng+1
	}
	cout<<sum%20123; //每次取完模的数加起来很可能大于20123,所以输出时再取一次模
	return 0;
}

问题 F:学校的小路 road


题目描述
福州时代中学的校园内有许多条路,校园的各个角落被这些路相连在一起,整个校园就像个迷宫一般。你闲着无聊,想研究如何不重复地经过每一条路。
我们把两条或以上的路相交的地方看做是一个点,路则看成是边。你必须找到一种能够恰好经过每条边一次的方法。
读入校园路径的描述,并计算出一种走法,使每条边都恰好被经过一次。你能从任何一个点开始,在任意一个点结束。
每一条边连接两个点,点用 1 到 500 标号(虽然校园内可能并没有 500 个点)。一个点上可连接任意多(≥1)条边。两点间可能有多条边。所有边都是连通的(也就是你可以从任意一条边到达另外所有的边)。
你的程序必须输出所走的路径(用路上依次经过的点号码表示)。我们如果把输出的路径看成是一个 500 进制的数,那么当存在多组解的情况下,输出 500 进制表示法中最小的一个(也就是输出第一位较小的,如果还有多组解,输出第二位较小的,等等)。
输入数据保证至少有一个解。

输入
第 1 行:一个整数 F,表示路径的数目。
第 2 到 F+1 行:每行两个整数i,j表示这条路连接 i 与 j 号点。

输出
输出应当有 F+1 行,每行一个整数,依次表示路径经过的点的编号。注意数据可能有多组解,但是只有上面题目要求的那一组解是认为正确的。

样例输入
9
1 2
2 3
3 4
4 2
4 5
2 5
5 6
5 7
4 6

样例输出
1
2
3
4
2
5
4
6
5
7

提示
对于所有数据,1≤F≤1024,1≤i,j≤500

题解:

算法流程(无向图):

1.判断奇点数。奇点数若为0则任意指定起点,奇点数若为2则指定起点为奇点。

2.开始递归函数Hierholzer(x):   循环寻找与x相连的边(x,u):
    删除(x,u)
    删除(u,x)
    Hierholzer(u);   将x插入答案队列之中

3.倒序输出答案队列

// F.图论模板题 :逐步插入回路法(Hierholzer算法)
#include<bits/stdc++.h>
using namespace std;
const int N=1025;
multiset<int> to[N];
int len[N];
int road[N],k;
void dfs(int x){
    for(auto a=to[x].begin();a!=to[x].end();a=to[x].begin()){//auto类型为C++11标准,可进行自动类型推断 
        int u=*a;
        to[x].erase(a);
        to[u].erase(to[u].find(x));//删边 
        dfs(u);//递归 
    }
    road[k++]=x;//往答案队列里插入答案 
}

int main(){
    int m,a,b;
    scanf("%d",&m);
    for(int i=0;i<m;i++){
        scanf("%d%d",&a,&b);
        len[a]++,len[b]++;
        to[a].insert(b);
        to[b].insert(a);
    }
    int s=-1,e=-1;//起点与终点 
    for(int i=1;i<=1024;i++)
        if(len[i]%2==1){
            if(s==-1)s=i;
            else if(e==-1)e=i;
            else exit(1);
        }//判断每个点的度数 
    if(s==-1)s=1;
    dfs(s);//开始递归 
    for(k=k-1;k>=0;k--)
        printf("%d\n",road[k]);//倒序输出答案 
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米莱虾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值