1. 程式人生 > >Wannafly 挑戰賽27 題解

Wannafly 挑戰賽27 題解

Wannafly 挑戰賽27

題目連線

A.灰魔法師

題目

在這裡插入圖片描述

題解

考慮到可能的完全平方數只有400400多個,因此對於每種數,直接暴力列舉所有的完全平方數計算一下就可以了.

程式碼

#include <iostream>
#define int long long
const int N = 100007;
int a[N];
int n;
int p2[N];
int tot;
signed main() {
    for(int i = 1;;i++) {
        int a = i*i;
        if(a > 2*N) break;
        p2[tot++
] = a; } std::cin >> n; for(int i = 1;i <= n;++i) { int tmp; std::cin >> tmp; a[tmp] ++; } int ans = 0; for(int i = 1;i <= 100000;++i) { if(a[i] == 0) continue; for(int j = 0;j < tot;++j) { int an = p2[j] - i; if
(an < i) continue; if(an == i) ans += a[i]*(a[i]-1)/2; else if(an <= 100000)ans += a[i]*a[an]; } } std::cout << ans << std::endl; }

B.紫魔法師

題目

在這裡插入圖片描述

題解

注意到至少當存在一個奇環的情況下,一定需要33種顏色,而其他情況下只要22種顏色就足夠了.

只需要用tarjan演算法求其點雙連通分量的大小即可.

程式碼

#include <iostream>
#include <algorithm> #include <cstring> #include <stack> #include <vector> #define pr(x) std::cout << #x << ':' << x << std::endl #define rep(i,a,b) for(int i = a;i <= b;++i) const int N = 100007; struct edge{ int u,v,nxt; }es[N<<2]; int head[N],cnt; int vis[N],dfn[N],low[N],idx; int v[N<<1],u[N<<1]; std::stack<int> stk; void addedge(int u,int v) { es[cnt].u = u;es[cnt].v = v;es[cnt].nxt = head[u];head[u] = cnt++; } int flag; void tarjan(int u,int fa) { dfn[u] = low[u] = ++idx; vis[u] = 1; for(int e = head[u];e != -1;e = es[e].nxt) { int v = es[e].v; if(v == fa) continue; stk.push(e); if(!vis[v]) { tarjan(v,u); if(low[u] > low[v]) low[u] = low[v]; if(dfn[u] <= low[v]) { //割點 int cnt = 0; while(true) { int se = stk.top();stk.pop(); cnt++; if(se == e) break; } if(cnt > 1 && cnt % 2 != 0) { flag = 1; } } } else low[u] = std::min(low[u],dfn[v]); } } int n,m; int main() { memset(head,-1,sizeof(head)); std::ios::sync_with_stdio(false); std::cin >> n >> m; rep(i,1,m) { int u,v; std::cin >> u >> v; addedge(u,v); addedge(v,u); } tarjan(1,0); if(flag) std::cout << "3" << std::endl; else std::cout << "2" << std::endl; return 0; }

C.藍膜法師

題目

在這裡插入圖片描述

題解

樹形dp

狀態定義

定義dp[u][i]dp[u][i]表示uu子樹中包含節點uu的連通塊大小為ii,且其餘聯通塊大小均k\le k的方案數.

注意上述定義中dp[u][0]dp[u][0]是沒有意義的,我們再定義dp[u][0]=t=1min{size[u],k}dp[u][t]dp[u][0] = \sum_{t = 1}^{min\{size[u],k\}} dp[u][t].

轉移方程的計算:

當我們要計運算元樹uudpdp值的時候,其兒子節點分別為v1,v2,...,vmv_1,v_2,...,v_m.

如果我們將通向兒子節點vv的某條邊切斷,那麼這個兒子對uu節點聯通塊大小的貢獻就沒了,但是它的方案數可以取dp[v][1..k]dp[v][1..k],這也就是我們定義dp[v][0]dp[v][0]的意義所在了,很巧妙地,dp[v][0]dp[v][0]就剛好等於兒子vvuu的聯通塊貢獻為00時的方案數.

那麼方程就得到了

dp[u][i]=t1+t2+...+tm=i1dp[v1][t1]dp[v2][t2]...dp[vm][tm]dp[u][i] = \sum_{t_1+t_2+...+t_m=i-1}dp[v_1][t_1]*dp[v_2][t_2]*...*dp[v_m][t_m]

最後答案就是dp[1][0]dp[1][0]

實現方式

我們可以先將v1v_1uu合併,再將v2v_2uu合併…

程式碼

#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
const int N = 2018;
typedef long long LL;
const LL P = 998244353;
std::vector<int> edge[N];
LL dp[N][N];
LL tmp[N];
int sz[N];
int n,k;
void dfs(int u,int fa) {
    sz[u] = 1;
    dp[u][1] = 1;
    for(int v : edge[u]) {
        if(v == fa) continue;
        dfs(v,u);
        memset(tmp,0,sizeof(tmp));
        for(int i = 1;i <= sz[u];++i) {
            for(int j = 0;j <= sz[v] && i + j <= k;++j) {
                tmp[i+j] = (tmp[i+j] + (dp[u][i] * dp[v][j] % P)) % P;
            }
        }
        for(int i = 1;i <= k;++i)
            dp[u][i] = tmp[i];
        sz[u] += sz[v];
    }
    for(int i = 1;i <= k;++i)
        dp[u][0] = (dp[u][0] + dp[u][i]) % P;
}
int main () {
    std::ios::sync_with_stdio(false);
    std::cin >> n >> k;
    for(int i = 0;i < n-1;++i) {
        int u,v;
        std::cin >> u >> v;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    dfs(1,0);
    std::cout << dp[1][0] << std::endl;
    return 0;
}