问题 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;
}