降智比賽題解

A. 氣球

\(f_i\) 表示最後一次選擇的是 \(i\) 的答案,暴力轉移是枚舉一個 \(j\),判斷 \(c_i,c_j\) 是否相同對應乘上係數轉移就行了。

我們記 \(mx_i\) 表示當前考慮完的所有 \(f\) 的時候 \(c_k = i\)\(f_k\) 的最大值,那麼我們轉移只需要知道 \(mx_{c_i}\) 和剩下的數的最大值就行了,分別記錄最大值和次大值是什麼即可。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
#define int LL
const int MAXN = 1e5 + 5;
int n,q,v[MAXN],c[MAXN];
LL val[MAXN],mx,cmx;

signed main(){
    scanf("%lld%lld",&n,&q);
    FOR(i,1,n) scanf("%lld",v+i);
    FOR(i,1,n) scanf("%lld",c+i);
    while(q--){
        LL a,b;scanf("%lld%lld",&a,&b);
        FOR(i,0,n) val[i] = -1e18;
        mx = 0;cmx = 1;
        LL ans = 0;
        FOR(i,1,n){
            LL f = std::max(b*v[i],val[c[i]]+a*v[i]);
            f = std::max(f,(mx==c[i]?val[cmx]:val[mx])+b*v[i]);
            ans = std::max(ans,f);
            val[c[i]] = std::max(val[c[i]],f);
            if(mx == c[i]) continue;
            if(val[c[i]] > val[mx]) cmx = mx,mx = c[i];
            else if(val[c[i]] > val[cmx]) cmx = c[i];
        }
        printf("%lld\n",ans);
    }
    return 0;
}

B. 遊戲

可以證明:一定是取了若干個整行+一個散行。

反證:假設兩行的元素分別是 \(a,b\),設分別取到了 \(i,j\),那麼我們只需要證明:

\[\begin{aligned} a_{i+1}-b_j \leq a_i+b_j\\ b_{j+1}-a_i \leq a_i + b_j \end{aligned} \]

整理可以得到:

\[\begin{aligned} a_{i+1}-a_i \leq 2b_j\\ b_{j+1}-b_j \leq 2a_i \end{aligned} \]

而由於 \(a,b\) 是從大到小排序的,所以有 \(a_{i+1}-a_i \leq 0,b_{j+1}-b_{j} \leq 0\),得證。

但是注意直接去取和最小的整行是不對的,因爲可能存在和很小但是前面很大的可能,比如

1e9 1e9 1e9
2e9 1e9 1

\(4\) 個數,如果直接貪心會全取第一行並且取第二行第一個,是 \(5 \times 10^9\),然而實際上應該是全取第二行再取第一行第一個,是 \(4 \times 10^9 + 1\)。其實搞兩個和相等的,但是一個很均勻一個不均勻的就能證萎。。

所以我們去枚舉沒有取滿的行是哪個,然後排個序就好了。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 1000+5;
int n,m,k;
int a[MAXN][MAXN];
LL sm[MAXN];

int main(){
    scanf("%d%d%d",&n,&m,&k);
    FOR(i,1,n) FOR(j,1,m) scanf("%d",&a[i][j]),sm[i] += a[i][j];
    if(k == n*m){
        LL sm = 0;
        FOR(i,1,n) FOR(j,1,m) sm += a[i][j];
        printf("%lld\n",sm);
        return 0;
    }
    FOR(i,1,n) std::sort(a[i]+1,a[i]+m+1),std::reverse(a[i]+1,a[i]+m+1);
    LL ans = 1e18;
    FOR(i,1,n){
        LL gx = 0;
        FOR(j,1,k%m) gx += a[i][j];
        std::vector<LL> S;
        FOR(j,1,n) if(j != i) S.pb(sm[j]);
        std::sort(all(S));
        FOR(i,0,k/m-1) gx += S[i];
        ans = std::min(ans,gx);
    }
    printf("%lld\n",ans);
    return 0;
}
// 選擇最大值最小的一行 刪除最大值

C. 樹

我們可以補集轉化計算相交的方案數,兩個鏈有交當且僅當某一個鏈的 \(\text{lca}\) 在另一個鏈上。枚舉在哪個鏈上,相當於要處理一下以某個點爲 \(\text{lca}\),長度爲 \(p/q\) 的鏈的個數和經過某個點,長度爲 \(p/q\) 的鏈的個數。但是注意這樣會算重兩條鏈的 \(\text{lca}\) 互相包含的情況,不難發現這種情況一定是兩條鏈 \(\text{lca}\) 相同的情況,也可以類似算出來。

需要卡常:能預處理的就不要每次都算。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 3000+5;
int n,p,q;

struct Edge{
    int to,nxt;
}e[MAXN<<1];
int head[MAXN],_cnt;

inline void add(int u,int v){
    e[++_cnt] = (Edge){v,head[u]};head[u] = _cnt;
    e[++_cnt] = (Edge){u,head[v]};head[v] = _cnt;
}

LL f[2][MAXN],g[2][MAXN];
int dep[MAXN];

inline void dfs1(int v,int fa=0){
    dep[v] = dep[fa]+1;
    for(int i = head[v];i;i = e[i].nxt){
        if(e[i].to == fa) continue;
        dfs1(e[i].to,v);
    }
}

bool flag = 0;
int cnt[MAXN];

inline void dfs(int v,int d=0,int fa=0){
    ++cnt[d];
    for(int i = head[v];i;i = e[i].nxt){
        if((flag||dep[e[i].to] > dep[v]) && e[i].to != fa) dfs(e[i].to,d+1,v);
    }
}

inline void gao1(int rt,int p,LL &f){
    CLR(cnt,0);dfs(rt);
    FOR(i,0,p) f += 1ll*cnt[i]*(cnt[p-i]-(i==p-i));
    for(int i = head[rt];i;i = e[i].nxt){
        if(dep[e[i].to] > dep[rt]){
            CLR(cnt,0);dfs(e[i].to,1);
            FOR(i,0,p) f -= 1ll*cnt[i]*(cnt[p-i]-(i==p-i));
        }
    }
}

inline void gao2(int rt,int q,LL &g){
    CLR(cnt,0);dfs(rt);
    FOR(i,0,q) g += 1ll*cnt[i]*(cnt[q-i]-(i==q-i));
    for(int i = head[rt];i;i = e[i].nxt){
        CLR(cnt,0);dfs(e[i].to,1,rt);
        FOR(i,0,q) g -= 1ll*cnt[i]*(cnt[q-i]-(i==q-i));
    }
}

int main(){
//    freopen("C.in","r",stdin);
//    freopen("C.out","w",stdout);
    scanf("%d%d%d",&n,&p,&q);
    FOR(i,2,n){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);
    }
    dfs1(1);
    // f: lca=i
    // g: 經過 i
    // 0: p
    // 1: q
    FOR(i,1,n) gao1(i,p,f[0][i]),gao1(i,q,f[1][i]);flag = 1;
    FOR(i,1,n) gao2(i,p,g[0][i]),gao2(i,q,g[1][i]);
    LL s1 = 0,s2 = 0;
    FOR(i,1,n) s1 += f[0][i],s2 += f[1][i];
    LL ans = 0;
    ans = s1*s2;
    FOR(i,1,n) ans -= f[0][i]*g[1][i];
    FOR(i,1,n) ans -= g[0][i]*f[1][i];
    FOR(i,1,n) ans += f[0][i]*f[1][i];
    printf("%lld\n",ans);
    return 0;
}

D. 小號

最大的構造方案一定是 2 3 4 ... n 1,答案的上界是 \(\frac{n(n-1)}{2}\)

我們找到最小的 \(t\),滿足 \(\frac{t(t-1)}{2} \geq s\),設 \(r = \frac{t(t-1)}{2}-s\)(也就是要減少的數量),那麼一定要有 \(r \leq t-1\),我們先將前 \(t\) 個位置按照 2 3 4 ... t 1 放置,後面的 \(a_i=i\)。我們考慮先交換 \(a_1\)\(a_t\),這樣可能會讓 \(r--\) (如果 \(t\) 是偶數),仍然有 \(r \leq t-1\),然後只需要對應交換 \(a_1\)\(a_r\) 就好了,這樣會讓答案減少 \(r-1\),交換的位置 \(\leq t\),不會超過 \(n\)

構造題還是要考慮如何構造最大值。。然後去減少。這題考慮增加就不是很簡單,還要努力分析一下缺少的個數來觀察是否可以多交換一些東西擴大操作空間。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 1e5 + 5;
int n,a[MAXN];
LL s;

int main(){
    scanf("%d%lld",&n,&s);
    if(s > 1ll*n*(n-1)/2){
        puts("nO 5oLuTi0N");
        return 0;
    }
    LL t = 1;
    while(t*(t-1)/2 < s) ++t;
    FOR(i,1,t-1) a[i] = i+1;a[t] = 1;
    FOR(i,t+1,n) a[i] = i;
    LL r = t*(t-1)/2-s;
    if(r){
        // r < t
        r -= !((t&1));
        std::swap(a[1],a[t]);
        // r <= t
        std::swap(a[1],a[r+1]);
    }
    FOR(i,1,n) printf("%d%c",a[i]," \n"[i==n]);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章