CCPC-Wannafly Summer Camp 2019 全记录

 // 7.19-7.29 东北大学秦皇岛校区十天训练营,题目都挂在了Vjudge上。训练期间比较忙,没空更博总结,回来继续补题消化。

Day1

这天授课主题是简单图论,节奏挺好,wls两小时理完图论里的基本知识点。

下午的赛题就偏入门了(简单图论无疑),只涉及到最短路问题和简单的搜索以及一些奇怪的技巧。(差分约束呢?最小生成树呢?强连通分量呢?)

 A - Jzzhu and Cities (补)

把火车线路加上跑Dijkstra就好了,标记火车线路,相等时也要push。在最短路上的火车线路不能被取消,剩下的全部能取消。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 200010;
struct Edge {
    int to;
    bool istrain;
    ll w;
    Edge(int v, bool is, ll ww):to(v), istrain(is), w(ww){}
    bool operator<(const Edge& a)const {
        if(w==a.w) return istrain;  // 非火车节点先更新
        return w > a.w;
    }
};
vector<Edge> G[maxn];

bool vis[maxn];
int d[maxn];

int Dijkstra() {
    memset(d, 0x3f, sizeof(d));
    memset(vis, 0, sizeof(vis));
    d[1] = 0;
    int res = 0;

    priority_queue<Edge> q;
    q.push(Edge(1, 0, 0));
    while(!q.empty()) {
        Edge tmp = q.top(); q.pop();
        int u = tmp.to;
        if(vis[u]) continue;

        vis[u] = 1;
    //    d[u] = tmp.w;
        if(tmp.istrain) ++res;

        for(int i=0;i<G[u].size();i++) {
            int v = G[u][i].to;
            if(!vis[v] && d[v]>=d[u]+G[u][i].w) {
                d[v] = d[u] + G[u][i].w;
                q.push(Edge(v, G[u][i].istrain, d[v]));
            }
        }

    }
    return res;

}

int main() {
    int n, m, k;
    cin>>n>>m>>k;
    int u, v, w;
    for(int i=0;i<m;i++) {
        scanf("%d %d %d", &u, &v, &w);
        G[u].push_back(Edge(v, 0, w));
        G[v].push_back(Edge(u, 0, w));
    }
    for(int i=0;i<k;i++) {
        scanf("%d %d", &v, &w);
        G[1].push_back(Edge(v, 1, w));
        // G[v].push_back(Edge(1, 1, w));
    }

    printf("%d
", k-Dijkstra());

    return 0;
}
View Code

 B - Phillip and Trains 

BFS 注意打标记!!!(虽然只是3*100的地图也要爆内存!)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int n, k, sx;
char mp[3][110];
bool vis[3][110];
struct node {
    int x, y;
    node(int _x, int _y):x(_x), y(_y) {}
};

bool check(int x, int y) {
    if(x<0 || x>2)
        return false;
    if(y>=n)
        return true;
    if(mp[x][y]=='.')
        return true;

    return false;
}

bool bfs() {
    queue<node> q;
    q.push(node(sx, 0));

    while(q.size()) {
        node now = q.front(); q.pop();
        if(now.y>=n) {
            return true;
        }

    //    printf("(%d,%d) -> ", now.x, now.y);
        
        int nx = now.x, ny = now.y+1;
        if(!check(nx, ny))  continue; // 向右走一步

        for(int i=-1;i<=1;i++) {      // 尝试三个方向移动
            nx = now.x + i;
            if(check(nx, ny) && check(nx, ny+1) && check(nx, ny+2) && !vis[nx][ny+2]) {
                q.push(node(nx, ny+2));
                vis[nx][ny+2] = 1;
            }
        }
    }
    return false;
}


int main() {
    int t; cin>>t;
    while(t--) {
        scanf("%d %d", &n, &k);
        getchar();
        memset(vis, 0, sizeof(vis));
        for(int i=0;i<3;i++) {
            scanf("%s", mp[i]);
            
            if(mp[i][0]=='s')
                sx = i;
            
        }
        printf("%s
", bfs()?"YES":"NO");
    }
    
    return 0;
}
View Code

 C - A Mist of Florescence (补)

构造题,技巧就是设计井字形的连通块,把其他颜色块涂到井字的格子上。

#include<iostream>
#include<cstdio>
using namespace std;

int a, b, c, d;
char ans[50][50];
void solve() {
    for(int i=1;i<=12;i++) {
        for(int j=1;j<50;j++) {
            if(i%2==1 && j%2==1 && a) ans[i][j] = 'A', --a;
            else ans[i][j] = 'D';
        }
    }

    for(int i=13;i<=24;i++) {
        for(int j=1;j<50;j++) {
            if(i%2==1 && j%2==1 && b) ans[i][j] = 'B', --b;
            else ans[i][j] = 'D';
        }
    }

    --c;
    --d;
    for(int i=25;i<=36;i++) {
        for(int j=1;j<50;j++) {
            if(i%2==1 && j%2==1 && c) ans[i][j] = 'C', --c;
            else ans[i][j] = 'D';
        }
    }

    for(int j=1;j<50;j++) {
        ans[37][j] = 'C';
    }

    for(int i=38;i<50;i++) {
        for(int j=1;j<50;j++) {
            if(i%2==1 && j%2==1 && d) ans[i][j] = 'D', --d;
            else ans[i][j] = 'C';
        }
    }


}
int main() {
    cin>>a>>b>>c>>d;
    solve();
    printf("49 49
");
    for(int i=1;i<50;i++) {
        for(int j=1;j<50;j++)
            printf("%c", ans[i][j]);
        printf("
");
    }
    
    return 0;
}
View Code

 E - Igor In the Museum 

DFS到墙的边界 对每块编号!

#include<iostream>
#include<cstdio>
using namespace std;

int n, m, k;
char mp[1010][1010];
int v[1010][1010], id;  // v[i][j]: mp[i][j]的分类编号   id: 当前编号
int res[1010*1010];        // res[id]: 第id块的答案

const int dx[] = {0, 0, 1, -1};
const int dy[] = {1, -1, 0, 0};

int ans;
void dfs(int x, int y) {
    if(mp[x][y]=='*') {
        ans++;
        return;
    }
    v[x][y] = id;
    for(int i=0;i<4;i++) {
        int nx = x+dx[i], ny = y+dy[i];
        if(nx>=0 && nx<m && ny>=0 && ny<n && !v[nx][ny]) {
            dfs(nx, ny);
        }
    }
}
int main() {
    scanf("%d %d %d", &m, &n, &k);
    getchar();
    for(int i=0;i<m;i++) {
        scanf("%s", mp[i]);
    }

    for(int i=0;i<m;i++) {
        for(int j=0;j<n;j++) {
            if(mp[i][j]=='.' && !v[i][j]) {
                ++id;
                ans = 0;
                dfs(i, j);
                res[id] = ans;
            } 
        }
    }

    while(k--) {
        int x, y;
        scanf("%d %d", &x, &y);
        printf("%d
", res[v[x-1][y-1]]);
    }

    return 0;
}
View Code

 F - The Cild and Toy (补)

贪心!由于每去掉一个点,等价于去掉了所有与它相连的边,就是问去掉全部边的最小代价。答案当然就是每条边两个节点权值小的那头的总和。都不用建图!!

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 1010;
int n, m;
int w[maxn];

int main() {
    cin>>n>>m;
    for(int i=1;i<=n;i++) {
        scanf("%d", &w[i]);
    }
    int u, v, ans = 0;
    for(int i=0;i<m;i++) {
        scanf("%d %d", &u, &v);
        ans += min(w[u], w[v]);
    }
    printf("%d
", ans);
    
    return 0;
}
View Code

 G - New Year Permutation

对连通部分排序就完事了

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
int n, num[310], id;
int mp[310][310];
bool vis[310];

int ans[310];
struct list {
    vector<int> num;
    vector<int> id;
}L[310];

void dfs(int x, int id) {
    vis[x] = 1;

    L[id].num.push_back(num[x]);
    L[id].id.push_back(x);

    for(int i=1;i<=n;i++) {
        if(mp[x][i]) {
            if(!vis[i])
                dfs(i, id);
        }
    }
}

int main() {
    cin>>n;
    for(int i=1;i<=n;i++) {
        scanf("%d", &num[i]);
    }
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=n;j++) {
            scanf("%1d", &mp[i][j]);
        }
    }

    for(int i=1;i<=n;i++) {
        if(!vis[i])
            dfs(i, ++id);
    }

    for(int i=1;i<=id;i++) {
        // for(int j=0;j<L[i].num.size();j++) {
        //     printf("%d:%d ", L[i].num[j], L[i].id[j]);
        // }
        // cout<<endl;
        
        sort(L[i].num.begin(), L[i].num.end());
        sort(L[i].id.begin(), L[i].id.end());


        for(int j=0;j<L[i].num.size();j++) {
            ans[L[i].id[j]] = L[i].num[j];
        }
    }
    for(int i=1;i<=n;i++) {
        printf("%d%c", ans[i], i!=n?' ':'
');
    }
    return 0;
}
View Code

 H - Alyona and the Tree (补)

从根开始dfs就好,每个节点为max(0LL, now+w[i])

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 100010;
typedef long long ll;

struct Edge {
    int to;
    ll w;
    Edge(int v, ll ww):to(v), w(ww) {}
};
vector<Edge> G[maxn];
int n, vw[maxn];
int ans;
void dfs(int u, int fa, ll now) {
    for(int i=0;i<G[u].size();i++) {
        int v = G[u][i].to;
        if(v!=fa) {
            if(vw[v]>=now+G[u][i].w) {
                --ans;
                // printf("%d->%d
", u, v);
                dfs(v, u, max(now+G[u][i].w, 0LL));
            }
        }
    }

}

int main() {
    cin>>n; ans = n;
    for(int i=1;i<=n;i++) {
        scanf("%d", &vw[i]);
    }
    int u, w;
    for(int i=2;i<=n;i++) {
        scanf("%d %d", &u, &w);
        G[i].push_back(Edge(u, w));
        G[u].push_back(Edge(i, w));
    }

    dfs(1, -1, 0);
    printf("%d
", ans-1);
    return 0;
}
View Code

 L - Love Triangle 

假的三元环??

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 5010;
int n, tot;
int love[maxn], low[maxn];
bool vis[maxn];
bool ans;
void dfs(int u) {
    if(ans) return;

    vis[u] = true;
    low[u] = ++tot;
    int v = love[u];
    if(v && !vis[v]) {
        dfs(v);
        if(love[v] && low[love[v]]==low[u]+2 && love[love[v]]==u) {
            ans = true;
            return;
        }
    }
}

int main() {
    cin>>n;
    for(int i=1;i<=n;i++) {
        scanf("%d", &love[i]);
    }
    ans = false;
    for(int i=1;i<=n;i++) {
        if(!vis[i] && !ans)
            dfs(i);
    }
    printf("%s
", ans?"YES":"NO");
    return 0;
}
View Code

 N - News Distribution 

并查集

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 500100;
int n, m;
int fa[maxn];
int g[maxn], cnt[maxn];

int Find(int x) {
    return fa[x]==x?x:(fa[x]=Find(fa[x]));
}

void Union(int x, int y) {
    int a = Find(x);
    int b = Find(y);
    if(a==b) return;
    fa[a] = b;
}

int main() {
    cin>>n>>m;
    for(int i=1;i<=n;i++) fa[i] = i;
    while(m--) {
        int k;
        scanf("%d", &k);
        for(int i=0;i<k;i++) {
            scanf("%d", &g[i]);
        }
        for(int i=1;i<k;i++) {
            Union(g[0], g[i]);
        }
    }
    for(int i=1;i<=n;i++) {
        cnt[Find(i)]++;
    }
    for(int i=1;i<=n;i++) {
        printf("%d%c", cnt[Find(i)], i!=n?' ':'
');
    }
    return 0;
}
View Code

 O - NP-Hard Problem (补)

二分图裸题。。。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 100010;
int n, m;
vector<int> G[maxn];
int deg[maxn];
int id[maxn];
vector<int> ans[2];
bool dfs(int u) {
    for(int i=0;i<G[u].size();i++) {
        int v = G[u][i];
        if(!id[v]) {
            id[v] = 3 - id[u];
            if(id[v]==1)
                ans[0].push_back(v);
            else
                ans[1].push_back(v);
            if(!dfs(v)) return false;
        } else if(id[v]==id[u]) return false;
    }
    return true;
}

int main() {
    cin>>n>>m;
    int u, v;
    for(int i=0;i<m;i++) {
        scanf("%d %d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
        ++deg[u];
    }
    for(int i=1;i<=n;i++) {
        if(!deg[i]) continue;

        if(!id[i]) {
            id[i] = 1;
            ans[0].push_back(i);
            if(!dfs(i)) 
                return 0 * printf("-1
");
        }


    }
    
    for(int k=0;k<=1;k++) {
        printf("%d
", ans[k].size());
        for(int i=0;i<ans[k].size();i++) {
            printf("%d%c", ans[k][i], i==ans[k].size()-1?'
':' ');
        }
    }

    return 0;
}
View Code

Day2

第二天的主题是简单数论,下午挂的是CF上的一场区域赛:2015 ACM National Contest Romania

注意文件读入要求!!!(第一题让我WA了4次样例。。。尝试两种方法写)

 A - Por Costel and Azerah

子序列里和为偶数的个数,简单dp(或组合数计算)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const int maxn = 1000100;
int arr[maxn], n;
ll a[maxn], b[maxn];

int main() {
    
    freopen("azerah.in", "r", stdin);
    freopen("azerah.out", "w", stdout);
        int t; cin>>t;
    while(t--) {
        scanf("%d", &n);
        for(int i=1;i<=n;i++) {
            scanf("%lld", &arr[i]);
        }
        a[1] = b[1] = 0;
        if(arr[1]%2==0) b[1] = 1;
        else a[1] = 1;
        for(int i=2;i<=n;i++) {
            if(arr[i]%2==0) {
                b[i] = (b[i-1]*2+1) % mod;
                a[i] = (a[i-1]*2)% mod;
            }else {
                b[i] = (a[i-1] + b[i-1]) %mod;
                a[i] = (a[i-1] + b[i-1] + 1) %mod;
            }
        //    cout<<b[i]<<endl;
        }
        cout<<b[n]<<endl;
    }
    return 0;
}

/*
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const int maxn = 1000100;
int arr[maxn], n;

ll pow(ll a, ll n) {
    ll res = 1;
    while(n) {
        if(n%2) {
            res = res * a %mod;
        }
        a = a*a % mod;
        n >>= 1;
    }
    return res;
}
int main() {
    freopen("azerah.in", "r", stdin);
    freopen("azerah.out", "w", stdout);
    
    int t; cin>>t;
    while(t--) {
        ll odd = 0, even = 0;
        scanf("%d", &n);
        for(int i=1;i<=n;i++) {
            scanf("%lld", &arr[i]);
            if(arr[i]%2==0) even++;
            else odd++;
        }
        if(odd==0)
            cout<<(pow(2, even)-1+mod)%mod<<endl;
        else
            cout<<(pow(2, odd-1)*pow(2, even)%mod-1+mod)%mod<<endl;
    }
    return 0;
}

 */
View Code

 B - Por Costel and the Algorithm (补)

给了一段Bellman-Ford代码,调整边的顺序让它最多执行两次。

记录最短路上的边即可,dfs输出边的顺序。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 100100;

int n, m;
struct Edge{
    int to;
    int id;
    ll w;
    Edge(int v, int i, ll _w):to(v), id(i), w(_w) {}
    bool operator<(const Edge& e)const {
        return w > e.w;
    }
};
vector<Edge> G[maxn];

ll d[maxn];
int e[maxn];  // e[v]: 连向v的边的编号
bool path[2*maxn];

void Dijkstra() {
    memset(d, 0x3f, sizeof(d));
    d[1] = 0;

    priority_queue<Edge> q;
    q.push(Edge(1, 0, 0));
    while(!q.empty()) {
        Edge tmp = q.top(); q.pop();
        int u = tmp.to;
        for(int i=0;i<G[u].size();i++) {
            int v = G[u][i].to;
            if(d[v]>d[u]+G[u][i].w) {
                d[v] = d[u] + G[u][i].w;

                path[e[v]] = 0;
                path[G[u][i].id] = 1;
                e[v] = G[u][i].id;

                q.push(Edge(v, 0, d[v]));
            }
        }
    }
}


void dfs(int u) {
    for(int i=0;i<G[u].size();i++) {
        int m = G[u][i].id, v = G[u][i].to;
        //printf("%d->%d %d", u, v, m);
        if(path[m]) {
            printf("%d ", m);
            dfs(v);
        }
    }
}

int main() {
    freopen("algoritm.in", "r", stdin);
    freopen("algoritm.out", "w", stdout);
    int t; cin>>t;
    while(t--) {
        memset(e, 0, sizeof(e));
        memset(path, 0, sizeof(path));
        for(int i=0;i<maxn;i++) G[i].clear();

        scanf("%d %d", &n, &m);
        for(int i=1;i<=m;i++) {
            int u, v; ll w;
            scanf("%d %d %lld", &u, &v, &w);
            G[u].push_back(Edge(v, i, w));
        }
        Dijkstra();

        dfs(1);
        for(int i=1;i<=m;i++) {
            if(!path[i])
                printf("%d ", i);
        }
        printf("
");
    }
    return 0;
}
View Code

 C - Por Costel and Bujor

 D - Por Costel and the Censorship Committee

 E - Por Costel and the Cipher

 F - Por Costel and the Alien Invasion

 G - Por Costel and the Orchard

 H - Por Costel and the Match

并查集裸题(可以用带权并查集做?)

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 100010;
int n, m;
int fa[maxn*2];
int Find(int x) {
    return fa[x]==x?x:(fa[x]=Find(fa[x]));
}
 
void Union(int x, int y) {
    int a = Find(x), b= Find(y);
    if(a!=b) 
        fa[a] = b;
}
 
int main() {
    freopen("meciul.in", "r", stdin);
    freopen("meciul.out", "w", stdout);
    int t; cin>>t;
    while(t--) {
        scanf("%d %d", &n, &m);
        for(int i=1;i<=2*n;i++) fa[i] = i;
        int x, y;
        while(m--) {
            scanf("%d %d", &x, &y);
            if(Find(x)!=Find(y) && Find(x+n)!=Find(y+n)) {
                printf("YES
");
                Union(x, y+n);
                Union(x+n, y);
            } else {
                printf("NO
");
            }
        }
    }
    return 0;
}
View Code

 I - Por Costel and the Match

求[n/i],分块(也可以只计算k=sqrt(n)的结果,ans*2 - k*k就是最终答案)

ll ans = 0;
for(int i=1;i<=n;) {
    ans += n/i * (n/(n/i) - i +1);
    i = n/(n/i) + 1;
}

 J - Por Costel and Pinball

 K - Por Costel and the Firecracker (补)

没有内存限制的话就是水题,可是完全没有优化空间。。。

百度之,学会了分块打表

把第一次查询看成了x1,debug了半天。。。

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 10000010;
const int mod = 10000003;
ll n, a, b, x1, q, q1;
ll ans[maxn/100+10];
void pre() {
    ans[0] = x1;
    ll last = x1;
    for(ll i=1;i<maxn;i++) {
        last = (last*i % n + a) % n;
        if(i%100==0) {
            ans[i/100] = last;
        }
    //    cout<<i<<' '<<ans[i]<<endl;
    }

}

ll cal(ll k) {
    if(k%100==0) return ans[k/100];
    
    ll last = ans[k/100];
    for(ll i=k/100*100+1;i<=k;i++) {
        last = (last*i % n + a) % n;
    }
    return last;
}

int main() {
    freopen("pocnitoare.in", "r", stdin);
    freopen("pocnitoare.out", "w", stdout);

    cin>>n>>a>>b>>x1>>q>>q1;

    pre();
    printf("%lld
", x1=cal(q1-1));  // 第一次查询是q1-1而不是x1-1!!!
    for(int i=1;i<q;i++) {
        q1 = (i*x1 % mod + b) % mod + 1;
        printf("%lld
", x1=cal(q1-1));

    }
    return 0;
}
View Code

 L - Por Costel and the Semipalindromes

WA了半天,原来是1左移出现了溢出,ll范围一定要写成 (1LL<<k)

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
char res[1010];
int tmp[1010];
int main() {
    freopen("semipal.in", "r", stdin);
    freopen("semipal.out", "w", stdout);
    int t; cin>>t;
    while(t--) {
        int n; ll k;
        scanf("%d %lld", &n, &k);
        
        res[1] = res[n] = 'a';

        if(k>(1LL<<(n-2))) {
            k -= (1LL<<(n-2));
            res[1] = res[n] = 'b';
        } 
        --k;
        for(int i=0;i<n-2;i++) {
            tmp[n-1-i] = (k>>i)&1;
        }
        for(int j=2;j<n;j++) {
            if(tmp[j]) {
                res[j] = 'b';
            } else {
                res[j] = 'a';
            }
        }

        for(int i=1;i<=n;i++) {
            putchar(res[i]);
        }
        puts("");
    }
    return 0;
}
View Code

Day3

题目来自于NAIPC 2016北美邀请赛。

 E - K-Inversions

给定一个只含有AB的字符串(长度不超过1000000),求间隔为1~n-1的B-A有多少对。

FFT模板题

 F - Mountain Scenes

给长度总和为n的木条以及相框的宽度w和高度h,问能组成多少种不同的山峰形状。

大家的签到dp题,我的自闭题。。。 j 没有从0开始样例死都调不出来 噗

也就是dp[i][0] = 1

dp[i][j] 为宽度 i 用了总长 j 的木条的方案数。 dp[i+1][j] = sum( dp[i][j-k] ) ( 0<=k<=h, j )

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll; 
const int maxn = 10010;
const int mod = 1e9+7;

int n, w, h;
ll dp[110][maxn];
int main() {
    
    cin>>n>>w>>h;
    // for(int i=0;i<=h && i<=w;i++) dp[i][0] = 1;
    dp[0][0] = 1;
    for(int i=1;i<=w;i++) {
         for(int j=0;j<=n;j++) {  // j=0 开始。。。坑死自己了
            for(int k=0;k<=h && k<=j;k++) {
                dp[i][j] = (dp[i][j] + dp[i-1][j-k]) % mod;
            }
        }
    }

    ll ans = 0;
    for(int i=1;i<=n;i++)
        ans = (ans + dp[w][i])%mod;

    cout<<(ans-min(n/w, h))%mod<<endl;
    return 0;
}
View Code

 G - Symmetry

平面上有 n 个点( n<=1000),问至少加入多少点,使全部点与某一个点中心对称,或者全部点关于某条过其中两点的直线对称。

计算几何。

对于要求1,枚举全部点再check剩下点是否中心对称,复杂度O(n^2logn),可行。

对于要求2,枚举两点所得直线再check剩下点是否关于直线对称,复杂度O(n^3logn),TLE。

看题解好像是把直线去重才能过。

 I - Tourists

 求树上所有整除关系的两对节点的最短路之和。

 计算所有符合条件的节点复杂度O(nlogn),树上两点 u, v 的最短路为 dep[u] + dep[v] - 2*dep[lca(u, v)] + 1。

 找了个LCA的板子就1A了 O.O (第四个板子是改进的树链剖分求LCA,复杂度O(n + mlogn),是其中最快的。)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 500010;
struct Edge {
    int to, next;
}edges[maxn];
int head[maxn], tot;

int fa[maxn], siz[maxn], dep[maxn], top[maxn];
// top[u] == u : u不是重儿子
// top[u] == fa: fa的重儿子是u
void add(int u, int v) {
    edges[++tot].to = v;
    edges[tot].next = head[u];
    head[u] = tot;
}

void dfs(int u) { // 求fa, siz, dep, top
    int maxSon = 0, son = 0;
    top[u] = u;
    siz[u] = 1;
    for(int i=head[u];i;i=edges[i].next) {
        int v = edges[i].to;
        if(v==fa[u]) continue;

        fa[v] = u;
        dep[v] = dep[u] + 1;
        dfs(v);
        siz[u] += siz[v];

        if(siz[v]>maxSon)
            maxSon = siz[son=v];
    }
    if(son)    // 重儿子
        top[son] = u;
}

int Find(int u) {
    return u==top[u]?u:top[u]=Find(top[u]);
}

int LCA(int u, int v) {
    if(Find(u)!=Find(v))
        return dep[top[u]]<dep[top[v]] ? LCA(u, fa[top[v]]):LCA(v, fa[top[u]]);
    else
        return dep[u]<dep[v] ? u:v;
}


int main() {
    int n;
    cin>>n;
    for(int i=0;i<n-1;i++) {
        int u, v;
        scanf("%d %d", &u, &v);
        add(u, v);
        add(v, u);
    }
    memset(fa, -1, sizeof(fa));
    dfs(1);

    long long ans = 0;
    for(int i=1;i<=n/2;i++) {
        for(int j=i*2;j<=n;j+=i) {
            ans += dep[i]+dep[j]-2*dep[LCA(i, j)] + 1;
        }
    }
    printf("%lld
", ans);
    return 0;
}
View Code

Day4

 A - One-dimensional Japanese Crossword

简单模拟题。

 B - Passwords

简单数学题。

 C - Journey

 E - Jamie and Alarm Snooze

简单模拟题。

#include<iostream>
#include<cstdio>
using namespace std;

bool check(int h, int m) {
//    cout<<h<<":"<<m<<endl;
    if(h%10==7 || m%10==7) return true;
    return false;
}
int main()
{
    int x, h, min;
    scanf("%d", &x);
    scanf("%d %d", &h, &min);

    int y = 0;
    while(!check(h, min)) {
        if(x==60) {
            if(--h<0) h = 23;
        } else {
            min -= x;
            if(min<0) {
                min += 60;
                if(--h<0) h = 23;
            }
        }
        ++y;
    }

    printf("%d
", y);
    return 0;
}
View Code

 F - Jamie and Binary Sequence (changed after round)

给定 n 和 k,将 n 分解成 k 个二次幂之和,求2的指数最小且字典序最大的一组解,不存在则输出 No。

思维题。

把 n 转成二进制,要保证最高次幂最小,当二进制位1个数小于k时,把最高位2^n拆成 2^(n-1) + 2^(n-1),这样多了一个1。

当拆分之后1的数量大于k时,要保证字典序最大,再从最低位开始拆分,每次只拆一个1,新的低位加2。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;

ll n;
int k, bits[100100], cnt, high, len;
int tmp[65];

int main()
{
    cin>>n>>k;
    ll nn = n;
    for(int i=0;nn;i++) {
        tmp[i] = nn&1;
        nn >>= 1; 
        len = i;
        if(tmp[i]) ++cnt;
    }
    for(int i=len;i>=0;i--) {
        bits[len-i] = tmp[i];
    }
    high = len;  // 记录最高位指数
    // for(int i=0;i<=len;i++) cout<<bits[i];

    if(cnt>k) return 0 * printf("No
");

    int pos = 0;
    while(cnt<k) {
        if(cnt+bits[pos]<=k) { // 最小化y=max(ai)
            cnt += bits[pos];
            bits[pos+1] += 2*bits[pos];
            bits[pos] = 0;
            ++pos;
            len = max(pos, len);   // 忘记更新,WA66组
        } else {
            pos = len; 
            while(!bits[pos]) --pos;
            while(cnt<k) {
                ++cnt;
                bits[pos] -= 1;
                bits[++pos] += 2;
                
            }
        }
    }

    printf("Yes
");
    int up = max(pos, len);
    for(int i=0;i<=up;i++) {
        for(int j=0;j<bits[i];j++)
            printf("%d ", high-i);
    }
    return 0;
}
View Code

 G - Jamie and Interesting Graph

 需要构造出一个n个节点m条边的图,满足最小生成树的权为素数,1到n的最短路为素数。

 构造1~n的一条链为最小生成树,权为p = 100019。剩下不停加边,边权为2p。

#include<iostream>
#include<cstdio>
using namespace std;
int p = 100019;
int main() {
    int n, m;
    cin>>n>>m;
    printf("%d %d
", p, p);
    printf("1 2 %d
", p-(n-2));
    for(int i=2;i<n;i++)
        printf("%d %d 1
", i, i+1);
    m -= n-1;

    for(int i=1;i<n && m;i++)
    for(int j=i+2;j<=n && m;j++) {
        printf("%d %d %d
", i, j, 2*p);
        --m;
        
    }

    return  0;
}
View Code

 H - 树上染色

 很经典的树形dp。

// f[u][j]: 以u为根节点,涂了 j 个黑点的子树对答案的最大贡献

对于一条边,左边子树有 x 个黑点,则右边有 k-x 个黑点,左边白点为 size[v] - x,右边白点为 n - size[v] - (k-x),按照题意计算这部分贡献 val。

状态转移方程为 f[u][j] = max( f[u][j], f[u][j-x] + f[v][x] + val),j 倒序枚举更新。

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#define max(x, y) ((x) > (y) ? (x) : (y))
#define min(x, y) ((x) < (y) ? (x) : (y))
using namespace std;
typedef long long ll;
const int maxn = 2010;
struct Edge {
    int u, w;
    Edge(int uu, int ww):u(uu), w(ww) {}
};
vector<Edge> G[maxn];
int siz[maxn], n, kk;
ll f[maxn][maxn];  // f[u][j]: 节点i含有j个黑点的子树对答案的最大贡献

void dfs(int u, int fa) {
    f[u][0] = f[u][1] = 0;
    siz[u] = 1;
    for(int i=0;i<G[u].size();i++) {
        int v = G[u][i].u;
        if(v==fa) continue;
        
        dfs(v, u);
        siz[u] += siz[v];
 
    }
    for(int i=0;i<G[u].size();i++) {
        int v = G[u][i].u, w = G[u][i].w;
        if(v==fa) continue;
        
        for(int j=min(kk, siz[u]);j>=0;j--) {
            for(int k=0;k<=min(j, siz[v]);k++) {
                if(f[u][j-k]!=-1) {
                    ll val = (1LL*k*(kk-k)+ 1LL*(siz[v]-k)*(n-kk-(siz[v]-k)))*w;
                    f[u][j] = max(f[u][j], f[u][j-k]+f[v][k]+val);
                }
            }
        }
    }
}


int main() {
    scanf("%d %d", &n, &kk);
    for(int i=1;i<n;i++) {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        G[u].push_back(Edge(v, w));
        G[v].push_back(Edge(u, w));

    }
    memset(f, -1, sizeof(f));
    dfs(1, -1);
    printf("%lld
", f[1][kk]);

    return 0;
}
View Code

Day5

Day6

原文地址:https://www.cnblogs.com/izcat/p/11274543.html