The 2021 ICPC Asia Jinan Regional Contest

补题C,J,K

题目链接:
https://pintia.cn/market/item/1459833348620926976
不要选时光机就不用花钱

一、C Optimal Strategy
在这里插入图片描述在这里插入图片描述
题意:
有 n 件物品,第 i 件的价值为 a[i]。A 和 B 轮流取物品,A 先手。每个玩家都要最大化自己取到的物品的价值和,求有多少种可能的游戏过程。
题解:
在偶数情况下:
只要有一个人选了当前最大的数,那么后面那个人就一定要跟着选一个一样大的 。不然一定会亏。比如说:1 1 2 2 3 3,如果先手选了3,后手一定会选3。所以在排列中最大的一定是成对在一起的。当我们把排列中最大的数删掉之后第二大的就一定会成对,删掉第二大的之后第三大的就一定会成对。所以不妨从最小的开始考虑,先把最小的放好,再把第二小的成对插进去,这一步等价于球不同,盒子不同,有空盒的球盒模型。依次推到下去我们可以得到数字全部都是偶数的推导公式。
在奇数情况下:
根据第三个样例就可以发现,存在奇数个的规则。1 1 1 1 1 4 4 5 8 9 9 10
存在单独的数字时我们发现第一个人拿了之后我必然比你多一个。这样可以得出结论当存在优先拿走最大单个数。放在排列中,单个的10必然是第一个因为它比所有的数字都大,而同样的单个的8一定会在1和4前面。而且可以发现必然是先手拿10,后手拿8,因为其中一定有偶数个数字。
所以整体的思路:第一步先放最小的,排列次数是个数的阶乘。然后依次按从小到大进行插空。如果摆放数字为奇数个,成对的先进行插空,最后一个插前面。(还要记得×全排列,因为所有的物品都不一样)。求 C(n,m)=n! / m!(n-m)! 时,题目是在模998244353的情况下,所以用费马小定理+快速幂求逆元计算的。
挡板法:
①n个相同的球放到m个不同的盒子,不允许有空盒子,不同的放法有:C(n-1,m-1)
②n个相同的球放到m个不同的盒子,允许有空盒子,不同的放法有:C(n+m-1,m-1)
PS:写的时候慎要用 ×=,我最后用 ans×=(…) 时候wa了一发,因为炸了long long

AC代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<set>

using namespace std;
typedef long long ll;
const int mod=998244353;
const int MAXN=1e6+10;
ll n,sum,ans;
ll num[MAXN],cs[MAXN],fac[MAXN],inv[MAXN];
//快速幂 
ll quick_mod(ll a,ll b)
{
	ll res=1;
	while(b){
		if(b&1)
			res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res%mod;
}
//费马小定理求逆元inv(x)≡x^(mod-2)(模mod),前提mod是质数 
ll niyuan(ll x)
{
	return quick_mod(x,mod-2)%mod;
}
//初始化阶乘以及逆元数组
void init()
{
	fac[1]=1;
	inv[0]=1;
	for(int i=2;i<MAXN;i++)
		fac[i]=fac[i-1]*i%mod;
	for(int i=1;i<MAXN;i++)
		inv[i]=niyuan(fac[i]);
}
//组合数C(x,y)
ll C(ll x,ll y)
{
	return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
int main()
{
	ios::sync_with_stdio(false);
	init();
	cin>>n;
	for(int i=1,x;i<=n;i++){
		cin>>x;
		cs[x]++;
	}
	for(int i=1;i<=n;i++){
		if(!cs[i]) continue;
		if(sum==0){
			sum+=cs[i];
			ans+=fac[cs[i]];
		}
		else{
			//挡板法组合数公式当允许有空时:C(n+m-1,m-1)
			ans=ans*fac[cs[i]]%mod*C(cs[i]/2+sum,sum)%mod;
			sum+=cs[i];
		}
	}
	cout<<ans%mod<<endl;
	return 0;
}

二、J Determinant
在这里插入图片描述在这里插入图片描述
题意:
给定一个矩阵,并且给出它的精确行列式的绝对值。判断行列式的值为正,则输出+,若为负,则输出-。
题解:
题目要求很简单,就是求行列式的值是否与给定的值相同。但是题目中有一句:“And it can be proved that ∣det(A)∣ has no more than 104 bits under the conditions in this problem. ”意思是行列式的值不超过104比特,就是值不超过210000=210×1000=103×1000=103000,10的后面3000个0,很大很大的数,所以不能单纯的进行大小比较。
思路就是先用高斯消元化成上三角矩阵求值。求值的过程中需要找一个合适的大模数取模求值。再使用大数取模的方式对给定的精确的行列式的值取模。然后再看他们是不是一样。
注意多组数据的输入需要初始化的问题。

AC代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<set>

using namespace std;
typedef long long ll;
const int MAXN=110;
const ll mod=1e9+7;
int t,n;
ll lm[MAXN][MAXN];
string s;
//初始化
void init()
{
	for(int i=0;i<=n;i++)
		for(int j=0;j<=n;j++)
			lm[i][j]=0;
}
//高斯消元带模数 
ll gauss(int cnt)
{
	ll ans=1;
	for(int i=1;i<cnt;i++)
	{
		for(int j=i+1;j<=cnt;j++)
		{
			while(lm[j][i])
			{
				ll l=lm[i][i]/lm[j][i];
				for(int k=1;k<=cnt;k++)
					lm[i][k]=(lm[i][k]-lm[j][k]*l)%mod;
				for(int k=1;k<=cnt;k++)
					swap(lm[i][k],lm[j][k]);
				ans*=-1;
			}
		}
	}
	for(int i=1;i<=cnt;i++)
		ans=(ans*lm[i][i]%mod+mod)%mod;
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	cin>>t;
	while(t--){
		init();
		cin>>n;
		cin>>s;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				cin>>lm[i][j];
			}
		}
		//处理大数
		ll ans=0;
		for(int i=0;i<s.length();i++){
			ans=((ans*10)%mod+(s[i]-'0'))%mod;
		}
		if (gauss(n)==ans) cout<<"+"<<endl;
		else cout<<"-"<<endl;
	}
	return 0;
}

三、K Search For Mafuyu
在这里插入图片描述
在这里插入图片描述
题意:
有n个房间,Kanade在1号房间,Mafuyu在剩下的n-1个房间,每个房间都联通的,可以看成一棵树,Mafuyu在每个房间的概率相等。问走最优策略的路径时的最小时间。
题解
通过样例与题意看出,就是遍历树的n个结点的时间,只要每次遍历都搜完子树的所有结点,答案都是相同的。所以遍历整棵树然后记录每个点到达的时间,求和之后乘概率。概率为 1/(n-1)
注意多组数据的输入需要初始化的问题。
AC代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<set>

using namespace std;
const int MAXN=110;
int t,n,ans,idx,step,sum;
int head[MAXN],used[MAXN];
struct node{
	int to,next;
}edge[MAXN<<1];
//无向图
void add(int x,int y)
{
	edge[++idx].next=head[x];
	edge[idx].to=y;
	head[x]=idx;
}
//初始化
void init()
{
	ans=0,idx=0,step=0,sum=0;
	memset(head,0,sizeof(head));
	memset(used,0,sizeof(used));
}
void dfs(int x)
{
	if(sum==n) return ;
	for(int i=head[x];i;i=edge[i].next){
		if(!used[edge[i].to]){
			used[edge[i].to]=1;
			sum++,step++;
			ans+=step;
			dfs(edge[i].to);//搜完回溯一步
			step++;
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin>>t;
	while(t--)
	{
		cin>>n;
		init();
		for(int i=1,x,y;i<n;i++){
			cin>>x>>y;
			add(x,y);
			add(y,x);
			//双向边,开两倍大的结构体以及加反向边
		}
		used[1]=1,sum++;
		dfs(1);
		double res=(double)ans;
		printf("%.10f\n",res/(n-1));
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值