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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM