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\)
可知:

  1. \(t_1+t_2\)必然为偶数
  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;
}

队友补的D

posted @ 2021-11-15 22:29  Paranoid5  阅读(1455)  评论(2编辑  收藏  举报