2021icpc济南题解
2021——\(icpc\)(济南)
签到签慢了,签完都五百名,本来已经绝望准备下场南京了。但还好C是博弈+组合数学。冲了出来,交的时候手都在抖。A了都没反应过来。喜得铜牌。
补题链接
K、Search For Mafuyu
给定一棵 n 个点的树,A 在 1 号点,B 的位置在 2-n 中均匀随机,A 不知道 B 的位置。现在 A 要去找 B,每秒可以走到一个相邻点,求在最优策略下的期望时间。
题解:刚开始一直没太懂最优策略是什么意思,瞎蒙了几个想法,一直不对。然后灵机一动,发现这个问题等价与遍历整棵树然后记录每个点到达的时间。求和之后乘上概率即可。现在就是解决怎样走会使这个求和最小,当时想的是贪心的走最近的,zzh直接就上机了,可能有点急躁,写了好一会没写出来。后来我上,写没写出来,这时候twh和我说多验证几个样例,发现只要是欧拉序就行(不会证明)。之后顺利上手1A。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll cnt = 0;
vector<ll>vec[105];
bool vis[105];
ll ans[105];
void dfs(ll s,ll fa){//根节点步数为0
++cnt;ans[s] = cnt;
ll len = vec[s].size();
for(int i = 0;i < len;i++){
if(vec[s][i] == fa)continue;
dfs(vec[s][i],s);
++cnt;//回溯也算步数
}
}
void solve(){
cnt = -1;memset(vis,0,sizeof(vis));
for(int i = 0;i <= 104;i++)vec[i].clear();
ll n;scanf("%lld",&n);
for(int i = 1;i <= n-1;i++){
ll u,v;scanf("%lld%lld",&u,&v);
vec[u].push_back(v);
vec[v].push_back(u);
}
dfs(1,0);
ll sum = 0;
for(int i = 1;i <= n;i++){
sum += ans[i];
}
printf("%.9lf\n",(sum*1.0)/((n-1)*1.0));
}
int main(){
ll t;scanf("%lld",&t);
while(t--)solve();
}
(^_^)
C、Optimal Strategy
有 n 件物品,第 i 件的价值为 a[i]。A 和 B 轮流取物品,A 先手。每个玩家都要最大化自己取到的物品的价值和,求有多少种可能的游戏过程。
题解:
签完到之后队友看D,我看C。我感觉C可以试一下。
这个题目思考过程大概是这样:
可以把拿起过程看作一个排列,比如说第一个样例:
2 2 1,1 2 2,因为不是同样的2所以,乘个全排列2!,就是4种。
现在只需要思考限制就行。
第二个样例每一种数都是偶数个,这其实已经是提示了。
在偶数情况下,只要有一个人选了当前最大的数,那么后面那个人就一定要跟着选一个一样大的 。不然一定会亏。
比如说: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(虽然对这题没用但似乎可以出新题),因为其中一定有偶数个数字。
这体现了一个什么问题?单个对排列个数没卵用,因为我们是把大的往小的插空,单个的放在最前面就好了(但是记得乘排列的时候不能省略这一位)。
综上,可以得出整个问题的流程。
我知道有些人是从大到小处理排列的,因为开始我也是。但是这样你会发现单个数字是非常棘手的。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N = 2e6+50,mod = 998244353;
ll fac[N],inv[N];
ll fpow(ll a,ll b){
ll res =1;
while(b){
if(b&1)res = res*a%mod;
b>>=1;
a = a*a%mod;
}
return res%mod;
}
void init(){
fac[0] = 1;
for(ll i = 1;i < N;i++){
fac[i] = fac[i-1]*i%mod;
}
inv[N-1] = fpow(fac[N-1],mod-2);
for(ll i = N-2;i >= 0;i--){
inv[i] = inv[i+1]*(i+1)%mod;
}
}
ll a[N],num[N] = {0};
ll cal(ll n,ll m){
// cout<<n<<' '<<m<<endl;
// return fac[n+m-1]*inv[m-1]%mod;
return fac[n+m-1]*inv[m-1]%mod*inv[n]%mod;
}
void solve(){
ll n;scanf("%lld",&n);
for(ll i = 1;i <= n;i++){
scanf("%lld",&a[i]);
num[a[i]]++;
}
ll ans = 1,sum = 0;
for(ll i = 1;i < N;i++){
if(num[i] != 0){
ll tot;
if(num[i]&1)tot = num[i]-1;
else tot = num[i];
ans = ans*cal(tot/2,sum+1)%mod;
// cout<<ans<<endl;
sum = sum+tot;
if(num[i]&1)sum++;
}
}
for(ll i = 1;i < N;i++){
ll tot = num[i];
// if(num[i]&1)tot--;
ans = ans*fac[tot]%mod;
}
printf("%lld\n",ans);
}
int main(){
init();solve();
return 0;
}
后来补的:
J、Determinant
给定一个矩阵,并且给出它的精确行列式的绝对值
判断行列式的正负
证明很简单大概如下:
求证:任意一对非0的相反数\(x_1,x_2\),有\(x_1\not\equiv x_2(\mod p)\),其中\(p\)为奇数,且\(x_1\mod p\not=0, x_2\mod p \not=0。\)
\(~~~~~x_1+x_2=0\)
=>\((x_1+x_2)\mod p =0\)
=>\((x_1\mod p+x_2\mod p)\mod p =0\)
假设\(x_1\equiv x_2(\mod p)\),
令\(t_1 = x_1\mod p,t_2 = x_2\mod p\)
则有\(t_1=t_2\),且\(t_1 < p,t_2<p\)
可知:
- \(t_1+t_2\)必然为偶数
- \(0<t_1+t_2<2p\)
若使得\((t_1+t_2 )\mod p = 0\),当且仅当\(t_1+t_2 = kp\)
由(2)可知\(0<k<2\),即\(k=1\),由此得\(t_1+t_2 = p\)
但是\(t_1+t_2\)为偶数,\(p\) 为奇数,矛盾,
故\(x_1\not\equiv x_2(\mod p)\)
当然这还需要找一个优秀的模数\(p\)使得\(p\)不是行列式的因子,虽然这个题没有卡模数
\(1e9+7\)就可以过。
题解:
这个题就是高斯消元求行列式。但是处理非常巧妙。其实高斯消元没咋学,就会一个板子,比赛的时候这个题就没想了。
这个要点是这样的:
一个数和他的相反数对一个奇数取模一定不同。
所以用高斯消元+取模的方式求一个行列式,再使用大数取模的方式对给定的精确行列式的绝对值取一个模。看他们是不是一样的就行。
因为不是自己想出来的所以就没那么多废话了
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
#define ll long long
const ll N = 105;
const ll mod = 1e9+7;
ll a[N][N];
ll fpow(ll b,ll n){
ll res = 1;
while(n){
if(n&1) res = 1ll*res*b%mod;
b = 1ll*b*b%mod;
n>>=1;
}
return res%mod;
}
ll Gauss(ll n){
ll det = 1;
for (ll i = 1; i <= n; ++i) {
ll k = i;
for (ll j = i + 1; j <= n; ++j)
if (abs(a[j][i]) > abs(a[k][i])) k = j;
if (abs(a[k][i]) == 0) {
det = 0;
break;
}
swap(a[i], a[k]);
if (i != k) det = -det;
det = (1ll*det*a[i][i]%mod+mod)%mod;
for (ll j = i + 1; j <= n; ++j) a[i][j] = 1ll*a[i][j]*fpow(a[i][i],mod-2)%mod;
for (ll j = 1; j <= n; ++j) {
if (j != i && a[j][i]) {
for (ll l = i + 1; l <= n; ++l) {
a[j][l] = (a[j][l] - 1ll*a[i][l] * a[j][i] % mod+mod) % mod;
}
}
}
}
return det;
}
char str[10005];
ll MOD(){
ll ans = str[0]-'0',len = strlen(str);
for(ll i = 1;i < len;i++){
ans = (ans*10%mod+str[i]-'0')%mod;
}
return ans;
}
int main() {
ll t;cin>>t;
while (t--) {
ll n;cin>>n;
cin>>str;
for (ll i = 1; i <= n; i++) {
for (ll j = 1; j <= n; j++) {
cin>>a[i][j];
}
}
ll det = Gauss(n);
// cout<<det<<' '<<MOD()<<endl;
puts(det==MOD()?"+":"-");
}
return 0;
}