The Preliminary Contest for ICPC Asia Xuzhou 2019

传送门

A. Who is better?

扩展中国剩余定理+斐波那契博弈,没啥好说的,关于斐波那契博弈,详见:传送门

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 15;
const ll MAX = 1e15;
int k;
ll a[N], b[N];

void exgcd(ll a, ll b, ll &x, ll &y) {
    if(b == 0) {
        x = 1, y = 0;
        return ;
    }
    exgcd(b, a % b, x, y);
    int t = x;
    x = y;
    y = t - (a / b) * y;
}

ll fib[100];
map <ll, bool> mp;
void pre() {
    fib[1] = fib[2] = 1;
    for(int i = 3; ;i++) {
        fib[i] = fib[i - 2] + fib[i - 1];
        if(fib[i] > MAX) break;
        mp[fib[i]] = 1;
    }
    mp[1] = 1;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    pre();
    cin >> k;
    for(int i = 1; i <= k; i++) {
        cin >> a[i] >> b[i];
    }
    ll n = b[1], m = a[1];
    for(int i = 2; i <= k; i++) {
        ll g = __gcd(m, a[i]);
        ll tmp = ((b[i] - n) % a[i] + a[i]) % a[i];
        if(tmp % g != 0) {
            cout << "Tankernb!"; return 0;
        }
        ll x, y;
        exgcd(m / g, a[i] / g, x, y);
        x = x * (tmp / g);
        x = (x % a[i] + a[i]) % a[i];
        n += x * m;
        m = m / g * a[i];
        n %= m;
        if(n > MAX) {
            cout << "Tankernb!"; return 0;
        }
    }
    if(mp[n]) cout << "Lbnb!";
    else cout << "Zgxnb!";
    return 0;
}

B. so easy

并查集+\(map\)就行,还可以离散化搞一下,把\(i+1\)顺便离散化一下就行。

Code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int MAXN = 1e6+5,MAXM = 1e6+5,MOD = 1e9+7,INF = 0x3f3f3f3f,N=100050;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
const db eps = 1e-9;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define vii vector<pair<int,int>>
#define vi vector<int>
using namespace std;
int n,q,X[MAXN<<1],len,fa[MAXN<<1],v[MAXN],v2[MAXN],mp[MAXN<<1];
struct Ques{
    int z,x;
}Q[MAXM];
int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
struct Istream {
    template <class T>
    Istream &operator >>(T &x) {
        static char ch;static bool neg;
        for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
        for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
        x=neg?-x:x;
        return *this;
    }
}fin;
int main(){
    //ios::sync_with_stdio(false);cin.tie(0);
    //freopen("../A.in","r",stdin);
    //freopen("../A.out","w",stdout);
    fin>>n>>q;
    for(register int i=1;i<=q;i++){
        fin>>Q[i].z>>Q[i].x;
        X[++len]=Q[i].x;
        X[++len]=Q[i].x+1;
    }
    sort(X+1,X+1+len);
    len=unique(X+1,X+1+len)-X-1;
    for(int i=1;i<=len;i++)fa[i]=i;
    for(int i=1;i<=q;i++){
        v[i] = lower_bound(X+1,X+1+len,Q[i].x)-X;
        v2[i] = lower_bound(X+1,X+1+len,Q[i].x+1)-X;
        mp[v[i]] = Q[i].x;
        mp[v2[i]] = Q[i].x+1;
    }
    int ans=0;
    for(int i=1;i<=q;i++){
        if(Q[i].z==1){
            if(fa[v[i]]==v[i])fa[v[i]] = find(v2[i]);
        }else{
            ans=mp[find(v[i])];
            if(ans==n+1)printf("-1\n");
            else printf("%d\n",ans);
        }
    }
    return 0;
}

C. Buy Watermelon

不知道啥题,队友说有点坑= =

Code
#include<bits/stdc++.h>
using namespace std;
int main() {
    int w; cin>>w;
    if(w>=4 && w%2==0) puts("YES");
    else puts("NO");
}

D. Carneginon

KMP模板题。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;

int q;
int nxtT[N], nxtS[N], nxt[N];

char T[N], S[N];

void Get_next(char *s, int *nxt) {
    int j, L = strlen(s + 1);
    nxt[1] = j = 0;
    for(int i = 2; i <= L; i++) {
        while(j && s[i] != s[j + 1]) j = nxt[j];
        if(s[i] == s[j + 1]) j++;
        nxt[i] = j;
    }
}

bool cmp(char *s1, char *s2, int op) {
    if(op) memcpy(nxt, nxtS, sizeof(nxtS));
    else memcpy(nxt, nxtT, sizeof(nxtT));
    int L1 = strlen(s1 + 1), L2 = strlen(s2 + 1);
    for(int i = 1, j = 0; i <= L1; i++) {
        while(j > 0 && (j == L2 || s1[i] != s2[j + 1])) j = nxt[j];
        if(s1[i] == s2[j + 1]) j++;
        if(j == L2) return true;
    }
    return false;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> T + 1 >> q;
    Get_next(T, nxtT);
    int lenT = strlen(T + 1);
    while(q--) {
        cin >> S + 1;
        int lenS = strlen(S + 1);
        Get_next(S, nxtS);
        if(lenS == lenT) {
            if(cmp(S, T, 1)) cout << "jntm!" << '\n';
            else cout << "friend!" << '\n';
        } else if(lenS < lenT) {
            if(cmp(T, S, 1)) cout << "my child!" << '\n';
            else cout << "oh, child!" << '\n';
        } else {
            if(cmp(S, T, 0)) cout << "my teacher!" << '\n';
            else cout << "senior!" << '\n';
        }
    }
    return 0;
}

E. XKC's basketball team

题意:
蔡徐坤的篮球队...
给出\(n\)个数,每个数为\(w_i\),现在对于第\(i\)个位置,找最远的一个\(j\),满足\(w_j\geq w_i+m\)

思路:
注意到这个\(j\)的选取具有单调性就行了,然后维护后缀最大值二分一下即可。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 5;

int n, m;
int w[N], mx[N];

bool chk(int x, int i) {
    return mx[x] >= w[i] + m;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> w[i];
    for(int i = n; i; i--) mx[i] = max(w[i], mx[i + 1]);
    for(int i = 1; i <= n; i++) {
        int l = i + 1, r = n + 1, mid;
        while(l < r) {
            mid = (l + r) >> 1;
            if(chk(mid, i)) l = mid + 1;
            else r = mid;
        }
        cout << l - 2 - i;
        if(i != n) cout << ' ';
    }
    return 0;
}

F. Little M's attack plan

题意:
给出一颗\(n\)个结点的树,每个结点有权值\(p_i\)
之后回答\(q\)个询问,每个询问给出\(v_i,k_i\),回答距离点\(v_i\)距离不大于\(k_i\)的所有点的权值和。
其中\(q\leq 5000,0\leq k_i\leq 100\)

思路:

  • 思考这样一个问题,假设询问只包含子树结点的时候怎么做?
  • 这样的做法有很多,可以主席树维护深度,在进入子树的时候统计一下答案,出子树的时候统计一下答案,两者相减即可。
  • 但为啥要用主席树,这种事不是权值线段树或者权值树状数组就行了么...
  • 现在回到原问题,注意\(q,k\)都比较小,两者相乘也才\(5e5\),所以想到将问题拆分!
  • 假设现在的询问为\((v,k)\),当前在\(v\)子树中深度相差不超过\(k\)的询问的答案为\(F(v,k)\)。那么我们求出了\(F(v,k)\),还需要求\(F(fa[v],k-1)\),之后有一部分重合,我们就减去\(F(v,k-2)\)...然后依次这样算就行了。

总的来说,就是利用容斥+拆分询问的思想,有时候一个询问比较复杂,可以考虑将询问拆分成多个,每次只回答一些子问题就行了。这跟多维偏序有点像。。
注意一下细节,比如\(k=0\)的情况什么的,以及跳到根就没必要再跳了。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000005;

template <class T>
inline void read(T& x) {
    static char c;
    x = 0;
    bool sign = 0;
    while (!isdigit(c = getchar()))
        if (c == '-')
            sign = 1;
    for (; isdigit(c); x = x * 10 + c - '0', c = getchar())
        ;
    if (sign)
        x = -x;
}

int n;
ll a[N];

struct Edge{
    int v, next;
}e[N << 1];
int head[N], tot;
void adde(int u, int v) {
    e[tot].v = v; e[tot].next = head[u]; head[u] = tot++;
}

int fa[N], d[N];
void getfa(int u, int Fa) {
    fa[u] = Fa; d[u] = d[Fa] + 1;
    for(int i = head[u]; i != -1; i = e[i].next) {
        int v = e[i].v;
        if(v != Fa) getfa(v, u);
    }
}

struct Query{
    int k, op, id;
};
vector <Query> q[N];

ll c[N];
int lowbit(int x) {return x & (-x);}

void add(int x, ll v) {
    for(; x < N; x += lowbit(x)) c[x] += v;
}

ll sum(int x) {
    ll ans = 0;
    for(; x; x -= lowbit(x)) ans += c[x];
    return ans;
}
ll sum(int l, int r) {
    return sum(r) - sum(l - 1);
}

ll res[N];

void dfs(int u, int fa) {
    for(auto it : q[u]) {
        int k = it.k, id = it.id, op = it.op;
        res[id] -= op * sum(d[u], d[u] + k);
    }
    add(d[u], a[u]);
    for(int i = head[u]; i != -1; i = e[i].next) {
        int v = e[i].v;
        if(v != fa) dfs(v, u);
    }
    for(auto it : q[u]) {
        int k = it.k, id = it.id, op = it.op;
        res[id] += op * sum(d[u], d[u] + k);
    }
}

int main() {
//    freopen("input.in", "r", stdin);
    read(n);
    for(int i = 1; i <= n; i++) read(a[i]);
    memset(head, -1, sizeof(head));
    for(int i = 1; i < n; i++) {
        int u, v; read(u), read(v);
        adde(u, v); adde(v, u);
    }
    getfa(1, 0);
    int t; read(t);
    for(int i = 1; i <= t; i++) {
        int v, k; read(v), read(k);
        q[v].push_back({k, 1, i});
        while(fa[v] && (--k) >= 0) {
            q[fa[v]].push_back({k, 1, i});
            if(k - 1 >= 0) q[v].push_back({k - 1, -1, i});
            v = fa[v];
        }
    }
//    for(int i = 1; i <= tot; i++) {
//        cout << q[i].v << ' ' << q[i].k << ' ' << q[i].op << '\n';
//    }
    dfs(1, 0);
    for(int i = 1; i <= t; i++) {
        printf("%lld\n", res[i]);
    }
    return 0;
}

G. Colorful String

在回文自动机上面\(dfs\)一下,统计一下即可。利用一个桶来维护一下当前出现的次数,注意还要乘上回文串出现的次数。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 5;

int n;
char s[N];

namespace PAM{
    int ch[N][26], fail[N], len[N], st[N], cnt[26], num[N];
    int sz, n, last;
    ll ans, cur;
    int New(int l, int f) {
        memset(ch[++sz], 0, sizeof(ch[sz]));
        len[sz] = l, fail[sz] = f;
        return sz;
    }
    void init() {
        sz = -1; ans = cur = 0;
        New(0, 1); last = New(-1, 0);
        st[n = 0] = -1;
        memset(cnt, 0, sizeof(cnt));
        memset(num, 0, sizeof(num));
    }
    int getf(int x) {
        while(st[n - len[x] - 1] != st[n]) x = fail[x];
        return x;
    }
    bool Insert(int c) { //int
        st[++n] = c;
        int x = getf(last);
        bool F = 0;
        if(!ch[x][c]) {
            F = 1;
            int f = getf(fail[x]);
            ch[x][c] = New(len[x] + 2, ch[f][c]);
        }
        last = ch[x][c];
        ++num[last];
        return F;
    }
    void count() {
        for(int i = sz; i >= 2; i--) num[fail[i]] += num[i];
    }
    void debug() {
        cout << sz << '\n';
        for(int i = 1; i <= sz; i++) cout << num[i] << ' ';
        cout << '\n';
    }
    void dfs(int u) {
        for(int i = 0; i < 26; i++) {
            int v = ch[u][i];
            if(v) {
                if(++cnt[i] == 1) ++cur;
                ans += cur * num[v];
                dfs(v);
                if(--cnt[i] == 0) --cur;
            }
        }
    }
};

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> s + 1;
    n = strlen(s + 1);
    PAM::init();
    for(int i = 1; i <= n; i++) {
        PAM::Insert(s[i] - 'a');
    }

    PAM::count();
    PAM::dfs(0);
    PAM::dfs(1);
    cout << PAM::ans;
    return 0;
}

H. function

参见:传送门

I. query

题意:
给出一个\(n\)的排列,现在回答多个询问,对于每个询问\([l,r]\),回答有多少对\((i,j)\),满足\(l\leq i < j\leq r\)\(min(p_i, p_j)=gcd(p_i,p_j)\)

思路:
注意到\(min(p_i, p_j)=gcd(p_i,p_j)\)其实就是\(a_i,a_j\)为倍数关系,因为\(1\)\(n\)的排列中这样的倍数对数量级为\(O(nlogn)\)的,所以我们可以提前找出来所有的对数。
然后将询问离线,就相当于处理一个简单的二位偏序问题了。
有很多种做法,余独爱树状数组。

Code
#include <bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 5;

int n, m;
int a[N], p[N];

vector <int> v[N];
vector <pii> Q[N];

int ans[N];

int c[N];

int lowbit(int x) {return x & (-x);}

void add(int x, int v) {
    for(; x < N; x += lowbit(x)) c[x] += v;
}

int query(int x) {
    int ans = 0;
    for(; x; x -= lowbit(x)) ans += c[x];
    return ans;
}

int query(int l, int r) {
    return query(r) - query(l - 1);
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        cin >> a[i]; p[a[i]] = i;
    }
    for(int i = 1; i <= n; i++) {
        for(int j = 2 * i; j <= n; j += i) {
            int x = p[i], y = p[j];
            if(x > y) swap(x, y);
            v[y].push_back(x);
        }
    }
    for(int i = 1; i <= m; i++) {
        int l, r; cin >> l >> r;
        Q[r].push_back(MP(l, i));
    }
    for(int i = 1; i <= n; i++) {
        for(auto it : v[i]) add(it, 1);
        for(auto it : Q[i]) {
            int L = it.fi, id = it.se, R = i;
            ans[id] = query(L, R);
        }
    }
    for(int i = 1; i <= m; i++) cout << ans[i] << '\n';
    return 0;
}

J. Random Access Iterator

题意:
现在有一个\(vector\),现在每次访问的时候会随机访问一个元素。
然后现在用这个\(vector\)去找子树最大深度,每到一个结点时,先找出\(size\),然后循环\(size\)次去找儿子\(dfs\)下去。
最后问得到的最大深度与实际真正的最大深度相等的概率为多少。

思路:
这个题我没想出来,dp这方面太弱了QAQ。
首先注意这一点:

  • 你成功一次就说明你成功了,要想失败,就得全部失败。

这不是什么励志鸡汤,这一点可以告诉我们将问题转化一下,求出失败的概率,那么减一下就有成功的概率了。
考虑树形dp,设\(dp[u]\)表示从\(u\)出发,能成功到达最大深度的概率。初始化,对于一些深度为最大深度的叶子结点,它们的值为\(1\),其余全是\(0\)
我们考虑对于每个结点,我们算它们失败一次的概率。那么就有:\(p=1-\frac{1}{size}\sum_{v\in son}dp[v]\)。这个还是比较好算的。
那么既然不能成功,就说明每次失败,那么全部失败的概率就为\(p^{size}\)。所以\(dp[u]=1-p^{size}\)
转化一下问题过后,就变得很简单了,如果硬要死磕成功,那么就还有第几次成功的问题,或者成功过后再成功什么乱七八糟的,就很难搞了。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5, MOD = 1e9 + 7;

ll qp(ll a, ll b) {
    ll ans = 1;
    while(b) {
        if(b & 1) ans = ans * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return ans;
}

int n;
vector <int> g[N];

int sz[N], dep[N];
int Max;

void dfs(int u, int fa) {
    dep[u] = dep[fa] + 1;
    for(auto v : g[u]) {
        if(v == fa) continue;
        dfs(v, u);
        ++sz[u];
    }
}

int dp[N];

int add(int x, int y) {
    x += y;
    if(x >= MOD) x -= MOD;
    return x;
}

int mul(ll a, ll b) {
    a *= b;
    return a % MOD;
}

void dfs2(int u, int fa) {
    if(dep[u] == Max) {
        dp[u] = 1; return ;
    }
    int p = 0;
    for(auto v : g[u]) {
        if(v == fa) continue;
        dfs2(v, u);
        p = add(p, dp[v]);
    }
    p = mul(p, qp(sz[u], MOD - 2));
    p = 1 - p + MOD;
    int k = qp(p, sz[u]);
    dp[u] = (1 - k + MOD) % MOD;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n;
    for(int i = 1; i < n; i++) {
        int u, v; cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, 0);
    Max = *max_element(dep + 1, dep + n + 1);
    dfs2(1, 0);
    cout << dp[1];
    return 0;
}

K. Center

题意:
给出\(n\)个点,\(n\leq 1000\),现在要在二维平面上找一个点\((x_0,y_0)\),使得对于每个点\((x_i,y_i)\),都存在一个点\((x_j,y_j)\)关于\((x_0,y_0)\)中心对称。
如果不存在一个点\((x_j,y_j)\),那么就称\((x_i,y_i)\)为孤儿点。
现在问怎么选择\((x_0,y_0)\),使得孤儿点最少。

思路:

  • 注意到我们选择的这个点\((x_0,y_0)\)肯定位于某些个点的中线上,并且这些点的个数越多越好。
  • 注意到点的个数很少,所以我们直接暴力枚举两个点,统计中点次数,然后更新答案就好了。

注意到如果这个点为某个\((x_i,y_i)\),那么还需要特判一下再更新答案。
详见代码:

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1005;

map <int, map<int, int>> mp, cnt;

struct node{
    int x, y;
}a[N];

int n;

bool chk(int x, int y) {
    return x % 2 == 0 && y % 2 == 0 && mp[x / 2][y / 2] == 1;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> a[i].x >> a[i].y;
        mp[a[i].x][a[i].y] = 1;
    }
    int ansx, ansy;
    int ans = 0;
    for(int i = 1; i <= n; i++) {
        for(int j = i; j <= n; j++) {
            int x = a[i].x + a[j].x, y = a[i].y + a[j].y;
            cnt[x][y]++;
            if(cnt[x][y] > ans) {
                ans = cnt[x][y];
                ansx = x, ansy = y;
            } else if(cnt[x][y] == ans && !chk(x, y)) {
                ans = cnt[x][y];
                ansx = x, ansy = y;
            }
        }
    }
    ans = n - 2 * cnt[ansx][ansy];
    if(chk(ansx, ansy)) ++ans;
    cout << ans;
    return 0;
}

M. Longest subsequence

序列自动机预处理下一位,然后在每一位都有两种选择,等于或大于,跟着\(t\)串跑一遍就行了。
注意一下细节,注意字典序不能相等,只有严格大于。
详见代码吧:

Code
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;

int nxt[N][26], last[26];

char s[N], t[N];

int n, m;

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    cin >> s + 1 >> t + 1;
    for(int i = n; i >= 0; i--) {
        int p = s[i] - 'a';
        for(int j = 0; j < 26; j++) nxt[i][j] = last[j];
        last[p] = i;
    }
    int i = 0;
    int ans = -1;
    for(int j = 1; j <= m; j++) {
        int tmp = N, p = t[j] - 'a';
        for(int k = p + 1; k < 26; k++) if(nxt[i][k]) tmp = min(tmp, nxt[i][k]);
        if(tmp < N) ans = max(ans, n - tmp + j);
        i = nxt[i][p];
        if(!i) break;
        if(j == m && i < n) {
            ans = max(ans, m + n - i);
        }
    }
    cout << ans;
    return 0;
}

posted @ 2019-09-13 17:34  heyuhhh  阅读(311)  评论(0编辑  收藏  举报