AGC038

Contest Page

开题开错翻车场.jpg

A

sol $A > \frac{W}{2}$或者$B > \frac{H}{2}$的时候无解,否则构造方法长下面这样

#include
using namespace std;

int main(){
    int H , W , A , B; cin >> H >> W >> A >> B;
    if(A > W / 2 || B > H / 2){puts("-1"); return 0;}
    for(int i = 1 ; i <= B ; ++i){
        for(int j = 1 ; j <= A ; ++j)
            putchar('0');
        for(int j = A + 1 ; j <= W ; ++j)
            putchar('1');
        putchar('\n');
    }
    for(int i = B + 1 ; i <= H ; ++i){
        for(int j = 1 ; j <= A ; ++j)
            putchar('1');
        for(int j = A + 1 ; j <= W ; ++j)
            putchar('0');
        putchar('\n');
    }
    return 0;
}

B

sol 我们令$i,j$相等当对$[i,i+K)$排序和对$[j,j+K)$排序后结果一样。我们考虑这样会有什么性质。

当这两个排序区间无交的时候,只有可能这两个区间都是升序的,否则不可能会相等;

如果两个排序区间有交,则一定满足$[j,i+K)$中的最小值大于$[i,j)$中的最大值,且$[i,j)$的元素升序排列;$[j,i+K)$中的最大值小于$[i+K,j+K)$中的最小值,且$[i+K,j+K)$的元素升序排列。那么不难得到$\forall x,y \in [i,j]$,$x$和$y$都是相等的,也就是原序列一定可以划分成若干段极大区间满足区间内任意两个位置相等。

用单调队列/set把这些区间找出来,然后把第二段中的情况稍加判断即可。

#include
using namespace std;

int main(){
    static int arr[200003]; int N , K;
    cin >> N >> K; for(int i = 1 ; i <= N ; ++i) cin >> arr[i];
    set < int > num; int ans = 1;
    for(int i = 1 ; i <= K ; ++i) num.insert(arr[i]);
    for(int i = K + 1 ; i <= N ; ++i){
        ans += *num.begin() != arr[i - K] || *--num.end() > arr[i];
        num.erase(arr[i - K]); num.insert(arr[i]);
    }
    bool flg = 0; int pre = -1 , cnt = 0;
    for(int i = 1 ; i <= N ; ++i){
        if(pre < arr[i]) ++cnt;
        else cnt = 1;
        pre = arr[i];
        if(cnt == K){flg = 1; --ans;}
    }
    cout << ans + flg; return 0;
}

C

sol $\begin{align*} \sum\limits_{i=1}^N \sum\limits_{j=i+1}^N lcm(A_i,A_j) & = \sum\limits_{i=1}^N \sum\limits_{j=i+1}^N \frac{A_iA_j}{gcd(A_i,A_j)} \\ & = \sum\limits_{d = 1}^{maxA} \frac{1}{d} \sum\limits_{d|A_i} \sum\limits_{d|A_j} A_iA_j[gcd(A_i,A_j)=d] \\ &= \sum\limits_{d = 1}^{maxA} \frac{1}{d} \sum\limits_{d|A_i} \sum\limits_{d|A_j} A_iA_j \sum\limits_{p | \frac{A_i}{d} , p | \frac{A_j}{d}} \mu(p) \\ &= \sum\limits_{p=1}^{maxA} \mu(p) \sum\limits_{d=1}^{\frac{maxA}{p}} \frac{1}{d} \sum\limits_{dp|A_i} \sum\limits_{dp|A_j} A_iA_j \\ &= \sum\limits_{T=1}^{maxA} \sum\limits_{p | T} \mu(p) \frac{p}{T} \sum\limits_{T|A_i} \sum\limits_{T|A_j} A_iA_j \end{align*}$

先对于每个$T$预处理$\sum\limits_{p | T} \mu(p) \frac{p}{T}$,然后注意到$\sum\limits_{T|A_i} \sum\limits_{T|A_j} A_iA_j = \frac{(\sum\limits_{T | A_i} A_i)^2 - \sum\limits_{T | A_i}A_i^2}{2}$,对于每一个$T$枚举倍数算出$\sum\limits_{T | A_i} A_i$和$\sum\limits_{T | A_i}A_i^2$即可。

复杂度可以做到$O(maxA\ log\ maxA)$,但是很呆的我后面一部分写的是枚举约数……
#include
using namespace std;

#define ll long long
const ll _ = 1e6 + 7 , MOD = 998244353 , INV2 = (MOD + 1) / 2;
int N , inv[_] , prm[_] , mu[_] , sum[_] , cnt; bool nprm[_];
int sum1[_] , sum2[_]; vector < int > ys[_];

void init(){
    mu[1] = 1;
    for(int i = 2 ; i <= 1e6 ; ++i){
        if(!nprm[i]){prm[++cnt] = i; mu[i] = -1;}
        for(int j = 1 ; prm[j] * i <= 1e6 ; ++j){
            nprm[prm[j] * i] = 1;
            if(i % prm[j] == 0) break;
            mu[i * prm[j]] = -mu[i];
        }
    }
    inv[1] = 1;
    for(int i = 2 ; i <= 1e6 ; ++i)
        inv[i] = MOD - 1ll * (MOD / i) * inv[MOD % i] % MOD;
    for(int i = 1 ; i <= 1e6 ; ++i)
        for(int j = i ; j <= 1e6 ; j += i){
            sum[j] = (sum[j] + 1ll * mu[i] * i + MOD) % MOD;
            ys[j].push_back(i);
        }
}

signed main(){
    init(); ios::sync_with_stdio(0);
    for(cin >> N ; N ; --N){
        int p; cin >> p;
        for(auto t : ys[p]){
            sum1[t] = (sum1[t] + p) % MOD;
            sum2[t] = (sum2[t] + 1ll * p * p) % MOD;
        }
    }
    int ans = 0;
    for(int i = 1 ; i <= 1e6 ; ++i)
        ans = (ans + 1ll * sum[i] * inv[i] % MOD * ((1ll * sum1[i] * sum1[i] - sum2[i] + MOD) % MOD * INV2 % MOD) % MOD) % MOD;
    cout << ans;
    return 0;
}

D

sol 先考虑$0$边。我们把答案图中的所有桥拿出来,那么$0$边的两个端点一定会在桥边形成的连通块上。用并查集并一下有哪些集合在同一个桥边连通块上。

那么对于$1$边来说,它们就不能在同一个桥边连通块上。这里判一下如果不合法输出No。
开这道题的时候盲猜$(A,B,1)+(B,C,1) \rightarrow (A,C,1)$然后就凉凉了

接下来考虑最小化和最大化边数方案。最小化边数的方案要么是树(就是没有$1$限制),要么就是一个基环树。最大化边数的方案就是所有桥边的连通块中选择一个点,然后这些点连完全图。
#include
using namespace std;

#define ll long long
#define PII pair < int , int >
const int _ = 1e5 + 7;
ll fa[_] , sz[_] , N , M , Q;
vector < PII > to0 , to1;

int find(int x){return fa[x] == x ? x : (fa[x] = find(fa[x]));}
void merge(int p , int q){if((p = find(p)) == (q = find(q))) return; fa[p] = q; sz[q] += sz[p];}

signed main(){
    ios::sync_with_stdio(0);
    cin >> N >> M >> Q;
    for(int i = 1 ; i <= N ; ++i) sz[fa[i] = i] = 1;
    for(int i = 1 ; i <= Q ; ++i){
        int A , B , C; cin >> A >> B >> C; ++A; ++B;
        if(C == 0) to0.push_back(PII(A , B));
        else to1.push_back(PII(A , B));
    }
    for(auto t : to0) merge(t.first , t.second);
    for(auto t : to1) if(find(t.first) == find(t.second)){puts("No"); return 0;}
    ll mn = 0 , mx = 0 , cnt = 0; bool flg = 1;
    for(int i = 1 ; i <= N ; ++i) if(find(i) == i){++cnt; mn += sz[i] - 1; mx += sz[i] - 1;}
    for(int i = 1 ; i <= N ; ++i) if(fa[i] == i) sz[i] = 1; else sz[i] = 0;
    for(auto t : to1) merge(t.first , t.second);
    for(int i = 1 ; i <= N ; ++i)
        if(fa[i] == i && sz[i] == 2 && cnt == 2){puts("No"); return 0;}
        else if(sz[i] >= 2) flg = 0;
    mn += cnt - flg; if(M < mn){puts("No"); return 0;}
    mx += cnt * (cnt - 1) / 2; if(M > mx){puts("No"); return 0;}
    puts("Yes"); return 0;
}

E

sol 要算最大值期望,考虑Min-Max容斥,也就是枚举每一个子集,计算其中存在一个满足要求时步数期望。不难发现这等价于方案经过的不存在任何一个位置满足要求的局面个数的期望。根据期望的线性性这等价于 每一种不存在任何一个满足要求的局面经过次数的期望之和

设枚举的子集下标是$p_1,p_2,...,p_k$,它们的$A$之和是$S_0$,我们枚举的局面中第$p_i$个位置随机到了$x_i$次。注意到在第一次到达当前局面之后停留在该局面的期望次数是$\sum\limits_{i=0}^\infty \left(\frac{S - S_0}{S}\right)^i = \frac{S}{S_0}$,所以我们只需要计算到达这个状态的概率。

显然在我们现在所要解决的问题下我们可以忽略没有随机在下标$p_1,p_2,...,p_k$内的操作。那么我们在操作序列中,$p_i$随到了$x_i$次,随到这个位置的概率是$\frac{A_{p_i}}{S_0}$,根据多重组合可以得到概率是$(\sum x_i)! \prod \frac{\left(\frac{A_{p_i}}{S_0}\right)^{x_i}}{x_i!}$

对于一个集合我们现在要求出所有$x_i$序列的期望之和。考虑DP:设$f_{i,j}$表示考虑了前$i$个物品,$\sum x_i = j$时的所有可能的序列的$\prod \frac{\left(\frac{A_{p_i}}{S_0}\right)^{x_i}}{x_i!}$之和,转移考虑下一个位置的$x_i$是多少。这个的复杂度是$O((\sum B_{p_i})^2)$的。

最后我们还要求出对于所有的序列$p_i$的方案数之和,稍微转换上面的DP:设$f_{i,j,k}$表示考虑了前$i$个位置,对于所有可能的$p$和$x$中满足$\sum A_{p_i} = j , \sum x_i = k$的方案中$\prod \frac{A_{p_i}^{x_i}}{x_i!}$的和,转移考虑当前物品是否加入$p$序列、加入多少个。不难证明复杂度是$O(\sum A_i (\sum B_i)^2)$的。
#include
using namespace std;

const int MOD = 998244353;
int dp[403][403] , A[403] , B[403] , jc[403] , inv[403] , N , S;

int poww(long long a , int b){
    int times = 1;
    while(b){
        if(b & 1) times = times * a % MOD;
        a = a * a % MOD; b >>= 1;
    }
    return times;
}

int main(){
    cin >> N; for(int i = 1 ; i <= N ; ++i){cin >> A[i] >> B[i]; S += A[i];}
    dp[0][0] = MOD - 1; jc[0] = 1;
    for(int i = 1 ; i <= 400 ; ++i) jc[i] = 1ll * jc[i - 1] * i % MOD;
    inv[400] = poww(jc[400] , MOD - 2);
    for(int i = 399 ; i >= 0 ; --i) inv[i] = inv[i + 1] * (i + 1ll) % MOD;
    for(int i = 1 ; i <= N ; ++i)
        for(int j = S ; j >= 0 ; --j)
            for(int k = 400 ; k >= 0 ; --k)
                if(dp[j][k])
                    for(int l = 0 , tms = 1 ; l < B[i] ; ++l , tms = 1ll * tms * A[i] % MOD)
                        dp[j + A[i]][k + l] = (dp[j + A[i]][k + l] - 1ll * tms * inv[l] % MOD * dp[j][k] % MOD + MOD) % MOD;
    int ans = 0;
    for(int i = 1 ; i <= S ; ++i){
        int val = poww(i , MOD - 2) , val1 = i == S ? 1 : 1ll * S * poww(i , MOD - 2) % MOD;
        for(int j = 0 ; j <= 400 ; ++j)
            ans = (ans + 1ll * dp[i][j] * jc[j] % MOD * poww(val , j) % MOD * val1) % MOD;
    }
    cout << ans; return 0;
}

F

sol 最小鸽模板题因为$10^5$不敢开……

考虑计算排列中最少的相同数字的数量。

考虑对于排列$P$连有向边$(i,P_i)$然后找到所有的环。显然对于排列$A$来说每一个环要么不转,要么沿着环转一格。对于$Q$和$B$也是一致的。

给$P,Q$中的每个环一个标号,设$fP_i$表示$P$中第$i$个环有没有转,$fQ_i$表示$Q$中第$i$个环有没有转。接下来考虑每一个位置$i$,设它在$P$的第$x$个环上、$Q$的第$y$个环上。考虑以下情况对位置相同值相同的数量的贡献:

1.如果$P_i = i , Q_i = i$,无论如何这里都会有$1$的贡献;
2.如果$P_i = i , Q_i \neq i$那么会在$fQ_y = 0$的时候产生$1$的贡献;
3.如果$P_i \neq i , Q_i = i$,那么会在$fP_x = 0$的时候产生$1$的贡献;
4.如果$P_i \neq i , Q_i \neq i$时,当$fP_x = 0 , fQ_y = 0$时会产生$1$的贡献;
5.在4的基础上如果$P_i = Q_i$,则在$fP_x = 1 , fQ_y = 1$时也会产生$1$的贡献。

看起来十分最小鸽,但是45的限制都是$fP_x$和$fQ_y$同时相等的时候才会有贡献,这个似乎不太会做。那么我们把$fQ_y$的意义反转一下,$fQ_y = 0$表示$y$环转了,这样就是两个位置不相同就会产生贡献,就是一个经典的最小鸽问题了。

所以为什么这个的复杂度还是$O(n^{1.5})$的啊QAQ
#include
using namespace std;

namespace flow{
    const int _ = 1e6 + 7 , __ = 1e7 + 7;
    struct Edge{int end , upEd , f;}Ed[__];
    int head[_] , dep[_] , cur[_] , cntEd = 1 , S , T;

    void addEd(int a , int b , int c){Ed[++cntEd] = (Edge){b , head[a] , c}; head[a] = cntEd;}
    void addE(int a , int b , int c){addEd(a , b , c); addEd(b , a , 0);}
    
    queue < int > q;
    bool bfs(){
        memset(dep , 0 , sizeof(int) * (T + 1)); dep[S] = 1;
        while(!q.empty()) q.pop(); q.push(S);
        while(!q.empty()){
            int t = q.front(); q.pop();
            for(int i = head[t] ; i ; i = Ed[i].upEd)
                if(Ed[i].f && !dep[Ed[i].end]){
                    dep[Ed[i].end] = dep[t] + 1;
                    if(Ed[i].end == T){memcpy(cur , head , sizeof(int) * (T + 1)); return 1;}
                    q.push(Ed[i].end);
                }
        }
        return 0;
    }

    int dfs(int x , int mn){
        if(x == T) return mn;
        int sum = 0;
        for(int &i = cur[x] ; i ; i = Ed[i].upEd)
            if(Ed[i].f && dep[Ed[i].end] == dep[x] + 1){
                int t = dfs(Ed[i].end , min(Ed[i].f , mn - sum));
                sum += t; Ed[i].f -= t; Ed[i ^ 1].f += t;
                if(mn == sum) break;
            }
        return sum;
    }

    int Dinic(int s , int t){S = s; T = t; int sum = 0; while(bfs()) sum += dfs(s , 1e9); return sum;}
}using flow::addE;
const int _ = 1e5 + 7;
int N , P[_] , Q[_] , belp[_] , belq[_];

int work(int *now , int *bel){
    int cnt = 0;
    for(int i = 1 ; i <= N ; ++i)
        if(!bel[i]){++cnt; int tmp = i; do{bel[i] = cnt; i = now[i];}while(i != tmp);}
    return cnt;
}

int main(){
    ios::sync_with_stdio(0); cin >> N;
    for(int i = 1 ; i <= N ; ++i){cin >> P[i]; ++P[i];}
    for(int i = 1 ; i <= N ; ++i){cin >> Q[i]; ++Q[i];}
    int szp = work(P , belp) , szq = work(Q , belq) , T = szp + szq + 1 , ans = 0;
    for(int i = 1 ; i <= N ; ++i)
        if(P[i] == i && Q[i] == i) ++ans;
        else if(P[i] == i) addE(szp + belq[i] , T , 1);
        else if(Q[i] == i) addE(0 , belp[i] , 1);
        else{
            addE(belq[i] + szp , belp[i] , 1);
            if(P[i] == Q[i]) addE(belp[i] , belq[i] + szp , 1);
        }
    cout << N - (ans + flow::Dinic(0 , T)); return 0;
}

你可能感兴趣的:(AGC038)