The Preliminary Contest for ICPC Asia Shanghai 2019 部分题解

开局G题不顺,然后卡D题,F水题没去切导致打崩了…其实好多可写题,只是这些可写题我不大擅长…
A. Lightning Routing I
待补

题目大意:给定一个边权树,要求支持动态修改边权,询问到某个点最远的点的距离
update:我去,怎么又是一道能写但是没读题的题,我如果卡题我们队就凉了我去…维护树的直径我在杭电多校9写过一个类似的:2019 Multi-University Training Contest 9 1007 Rikka with Travels,首先两颗子树合并,其新的树直径两端点必定在原来两颗子树直径的四个端点之中,知道了这个就很好想了,带修改我们只要一个树剖就可以解决树上任意两点距离了,然后用一个线段树维护树上dfs序的直径,就完事了,思维上也没啥难度
#include<bits/stdc++.h>
#define pi pair<int, int>
#define mk make_pair
#define ll long long
#define ls o * 2
#define rs o * 2 + 1
#define mid (l + r) / 2
using namespace std;
const int maxn = 1e5 + 10;
vector<pi> G[maxn];
pi W[maxn];
int dep[maxn], son[maxn], f[maxn], sz[maxn], cnt;
int id[maxn], top[maxn], n, val[maxn];
ll sum[maxn * 4];
void up(int o, int l, int r, int k, int v) {
    if (l == r) {
        sum[o] = v;
        return;
    }
    if (k <= mid)
        up(ls, l, mid, k, v);
    else
        up(rs, mid + 1, r, k, v);
    sum[o] = sum[ls] + sum[rs];
}
ll qu(int o, int l, int r, int ql, int qr) {
    if (ql > qr)
        return 0;
    if (l >= ql && r <= qr)
        return sum[o];
    ll res = 0;
    if (ql <= mid)
        res += qu(ls, l, mid, ql, qr);
    if (qr > mid)
        res += qu(rs, mid + 1, r, ql, qr);
    return res;
}
void dfs(int u, int fa, int deep, int v)
{
    val[u] = v;
    f[u] = fa;
    dep[u] = deep;
    sz[u] = 1;
    son[u] = 0;
    for(auto tmp : G[u]) {
        int v = tmp.first;
        if(v == fa)
            continue;
        dfs(v, u, deep + 1, tmp.second);
        sz[u] += sz[v];
        if(sz[son[u]] < sz[v])
            son[u] = v;
    }
}
void dfs1(int u, int rt)
{
    id[u] = ++cnt;
    top[u] = rt;
    up(1, 1, n, cnt, val[u]);
    if(son[u])
        dfs1(son[u], rt);
    for(auto tmp : G[u]) {
        int v = tmp.first;
        if(v == f[u] || v == son[u])
            continue;
        dfs1(v, v);
    }
}
ll dist(int x, int y) {
    ll res = 0;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]])
            swap(x, y);
        res += qu(1, 1, n, id[top[x]], id[x]);
        x = f[top[x]];
    }
    if (id[x] > id[y])
        swap(x, y);
    return res + qu(1, 1, n, id[x] + 1, id[y]);
}
struct node {
    int x, y;
    ll len;
    node operator+(const node& t) const {
        node tmp = t;
        if (len > t.len)
            tmp = node{x, y, len};
        ll dist1 = dist(x, t.x);
        ll dist2 = dist(x, t.y);
        ll dist3 = dist(y, t.x);
        ll dist4 = dist(y, t.y);
        if (dist1 > tmp.len)
            tmp = node{x, t.x, dist1};
        if (dist2 > tmp.len)
            tmp = node{x, t.y, dist2};
        if (dist3 > tmp.len)
            tmp = node{y, t.x, dist3};
        if (dist4 > tmp.len)
            tmp = node{y, t.y, dist4};
        return tmp;
    }
} tree[maxn * 4];
int w[maxn], L[maxn], R[maxn], dfn, rk[maxn];
void dfs2(int u, int fa) {
    L[u] = ++dfn;
    rk[dfn] = u;
    for (auto tmp : G[u])
        if (tmp.first != fa)
            dfs2(tmp.first, u);
    R[u] = dfn;
}
void build(int o, int l, int r) {
    if (l == r) {
        tree[o] = node{rk[l], rk[l], -1};
        return;
    }
    build(ls, l, mid);
    build(rs, mid + 1, r);
    tree[o] = tree[ls] + tree[rs];
}
void update(int o, int l, int r, int ql, int qr) {
    if (l >= ql && r <= qr)
        return;
    if (ql <= mid)
        update(ls, l, mid, ql, qr);
    if (qr > mid)
        update(rs, mid + 1, r, ql, qr);
    tree[o] = tree[ls] + tree[rs];
}
int main() {
    int u, v, q, x, val;
    char c;
    scanf("%d", &n);
    for (int i = 1; i < n; i++) {
        scanf("%d%d%d", &u, &v, &w[i]);
        G[u].push_back(mk(v, w[i]));
        G[v].push_back(mk(u, w[i]));
        W[i] = mk(u, v);
    }
    dfs(1, 0, 0, 0);
    dfs1(1, 1);
    dfs2(1, 0);
    build(1, 1, n);
    scanf("%d", &q);
    while (q--) {
        getchar();
        scanf("%c", &c);
        if (c == 'Q') {
            scanf("%d", &u);
            printf("%lld\n", max(dist(u, tree[1].x), dist(u, tree[1].y)));
        }
        else {
            scanf("%d%d", &x, &val);
            u = W[x].first;
            v = W[x].second;
            if (f[u] != v)
                swap(u, v);
            up(1, 1, n, id[u], val);
            update(1, 1, n, L[u], R[u]);
        }
    }
}

C. Triple

题意:从三个数组中各选一个数,要求三个数当中最大的数要小于等于另外两个数的和,问有多少种选法。
解法:和 hdu 4609 3-idiots解法类似,枚举每个数作为最大数mx的贡献,用fft求出另外两个数组的多项式乘积,前缀和预处理一下,然后sum[n] - sum[mx - 1]求出两数之和大于最大数mx的数量,然后简单去去重就可以了,不难,不过有点注意的是当 n 比较小的时候暴力更优
#include<bits/stdc++.h>
#define dob complex<double>
const int maxn = 4e5 + 10, N = 2e5;
using namespace std;
const double pi = acos(-1.0);
typedef long long ll;
ll num[maxn];
ll work(int *a, int *b, int *c, int n) {
   ll ans = 0;
   for (int i = 1; i <= n; i++) {
       ans += num[N] - num[c[i] - 1];
       int t1 = lower_bound(a + 1, a + 1 + n, c[i]) - a;
       int t2 = lower_bound(b + 1, b + 1 + n, c[i]) - b;
       t1 = n - t1 + 1;
       t2 = n - t2 + 1;
       ans += 1ll * t1 * t2;
       ans -= 1ll * (t1 + t2) * n;
   }
   return ans;
}
int r[maxn];
ll cnt1[maxn], cnt2[maxn], cnt3[maxn];
dob arr1[maxn], arr2[maxn], arr3[maxn], arr[maxn];
void fft(dob *A, int opt, int n) {
   for (int i = 0; i < n; i++)
       if (i < r[i])
           swap(A[i], A[r[i]]);
   for (int i = 1; i < n; i <<= 1) {
       dob wn(cos(pi / i), sin(opt * pi / i)), x, y;
       for (int j = 0; j < n; j += (i << 1)) {
           dob w(1, 0);
           for (int k = 0; k < i; k++, w *= wn) {
               x = A[j + k], y = w * A[i + j + k];
               A[j + k] = x + y;
               A[i + j + k] = x - y;
           }
       }
   }
}
ll cat(dob *A, dob *B, int *a, int *b, int *c, int n, int m) {
   for (int i = 0; i < n; i++)
       arr[i] = A[i] * B[i];
   fft(arr, -1, n);
   for (int i = 0; i < n; i++)
       num[i] = ll(arr[i].real() / n + 0.5);
   for (int i = 1; i <= N; i++)
       num[i] += num[i - 1];
   return work(a, b, c, m);
}
int a[maxn], b[maxn], c[maxn];
ll fuck(int n) {
   int m = n;
   ll ans = 0;
   int mx = 2e5 + 1, bit = 0;
   memset(cnt1, 0, sizeof(cnt1));
   memset(cnt2, 0, sizeof(cnt2));
   memset(cnt3, 0, sizeof(cnt3));
   for (int i = 1; i <= n; i++) {
       cnt1[a[i]]++;
       cnt2[b[i]]++;
       cnt3[c[i]]++;
   }
   for (n = 1; n <= mx; n <<= 1)
       bit++;
   for (int i = 0; i < n; i++)
       r[i] = (r[i >> 1] >> 1) | (i & 1) << (bit - 1);
   for (int i = 0; i < n; i++) {
       arr1[i] = cnt1[i];
       arr2[i] = cnt2[i];
       arr3[i] = cnt3[i];
   }
   fft(arr1, 1, n);
   fft(arr2, 1, n);
   fft(arr3, 1, n);
   ans += cat(arr1, arr2, a, b, c, n, m);
   ans += cat(arr2, arr3, b, c, a, n, m);
   ans += cat(arr3, arr1, c, a, b, n, m);
   return ans;
}
ll calc(int *a, int *b, int *c, int n) {
   memset(num, 0, sizeof(num));
   for (int i = 1; i <= n; i++)
       for (int j = 1; j <= n; j++)
           num[a[i] + b[j]]++;
   for (int i = 1; i <= N; i++)
       num[i] += num[i - 1];
   return work(a, b, c, n);
}
ll gao(int n) {
   ll ans = 0;
   ans += calc(a, b, c, n);
   ans += calc(c ,a, b, n);
   ans += calc(b, c, a, n);
   return ans;
}
ll gank(int n) {
   ll ans = 0;
   for (int i = 1; i <= n; i++) {
       int t1 = lower_bound(b + 1, b + 1 + n, a[i]) - b;
       int t2 = lower_bound(c + 1, c + 1 + n, a[i]) - c;
       int t3 = lower_bound(b + 1, b + 1 + n, a[i] + 1) - b;
       int t4 = lower_bound(c + 1, c + 1 + n, a[i] + 1) - c;
       if (b[t1] == a[i] && c[t2] == a[i])
           ans += 1ll * (t3 - t1) * (t2 - 1) + 1ll * (t4 - t2) * (t1 - 1) + 1ll * (t3 - t1) * (t4 - t2);
       else if (b[t1] == a[i])
           ans += 1ll * (t3 - t1) * (t2 - 1);
       else if (c[t2] == a[i])
           ans += 1ll * (t4 - t2) * (t1 - 1);
       int k1 = lower_bound(c + 1, c + 1 + n, b[i]) - c;
       int k2 = lower_bound(a + 1, a + 1 + n, b[i]) - a;
       int k3 = lower_bound(c + 1, c + 1 + n, b[i] + 1) - c;
       if (c[k1] == b[i])
           ans += 1ll * (k3 - k1) * (k2 - 1);
   }
   return ans;
}
int main() {
   int T, Case = 0;
   scanf("%d", &T);
   while (T--) {
       int n;
       scanf("%d", &n);
       for (int i = 1; i <= n; i++)
           scanf("%d", &a[i]);
       for (int i = 1; i <= n; i++)
           scanf("%d", &b[i]);
       for (int i = 1; i <= n; i++)
           scanf("%d", &c[i]);
       sort(a + 1, a + 1 + n);
       sort(b + 1, b + 1 + n);
       sort(c + 1, c + 1 + n);
       a[n + 1] = b[n + 1] = c[n + 1] = 0;
       printf("Case #%d: ", ++Case);
       if (n <= 1000)
           printf("%lld\n", gao(n) + gank(n));
       else
           printf("%lld\n", fuck(n) + gank(n));
   }
}

D. Counting Sequences I

题意:统计有多少个序列满足和等于积
解法:刚开始想dp写这题,想了巨久想不出来有效的dp,后来才想着暴力试一试,然后发现跑的贼快,既然是暴力题,那就没什么好说的了…
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 6001, mod = 1e9 + 7;
int ans[maxn], p[maxn], inv[maxn];
void add(int &x, int y) {
    x += y;
    if (x >= mod)
        x -= mod;
    if (x < 0)
        x += mod;
}
ll ksm(ll x, int y) {
	ll res = 1;
	while (y) {
		if (y & 1)
			res = res * x % mod;
		x = x * x % mod;
		y /= 2;
	}
	return res;
}
void dfs(int x, int n, int val, int num, int len, int mu, int sum) {
	if (val > 2 * n)
		return;
	if (val > 1 && val > sum) {
		int m = val - sum + len;
		if (m > 3000)
			return;
		int tmp = 1ll * p[m] * mu % mod * inv[num] % mod * inv[val - sum] % mod;
		add(ans[m], tmp);
	}
	dfs(x, n, val * x, num + 1, len + 1, mu, sum + x);
	for (int i = x + 1; i <= n; i++)
		dfs(i, n, val * i, 1, len + 1, 1ll * mu * inv[num] % mod, sum + i);
}
int main() {
	p[0] = inv[0] = 1;
	for (int i = 1; i <= 6000; i++) {
		p[i] = 1ll * p[i - 1] * i % mod;
		inv[i] = ksm(p[i], mod - 2);
	}
	dfs(2, 3000, 1, 0, 0, 1, 0);
	int T;
	ans[2] = 1;
	scanf("%d", &T);
	while (T--) {
		int n;
		scanf("%d", &n);
		printf("%d\n", ans[n]);
	}
}

E. Counting Sequences II

题意:问有多少个长度为n的序列,每个数的范围是1~m, 偶数在序列中出现次数必须是偶数
解法:生成函数裸题,我们求出偶数数量 t = m / 2 t=m/2 ,构造生成函数:
g ( x ) = ( 1 + x 2 2 ! + x 4 4 ! + . . . . ) t ( 1 + x 1 1 ! + x 2 2 ! + x 3 3 ! + . . . ) m t g(x) =(1 + \frac{x^2}{2!} +\frac{x^4}{4!} +....)^{t}(1+\frac{x^1}{1!}+\frac{x^2}{2!}+\frac{x^3}{3!}+...)^{m-t}
简单变形: g ( x ) = 1 2 ( e x + e x ) t e m x t x = 1 2 t e m x t x i = 0 t C t i e t x i x e i x g(x) =\frac{1}{2}(e^{x}+e^{-x})^{t}e^{mx-tx}=\frac{1}{2^{t}}e^{mx-tx}\sum_{i=0}^{t}C_t^ie^{tx-ix}e^{-ix}
最终化简: g ( x ) = 1 2 t i = 0 t C t i e m x 2 i x g(x)=\frac{1}{2^{t}}\sum_{i=0}^{t}C_t^ie^{mx-2ix}
很明显: a n s = 1 2 t i = 0 t C t i ( m 2 i ) n ans=\frac{1}{2^{t}}\sum_{i=0}^{t}C_t^i(m-2i)^{n}
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5 + 10, mod = 1e9 + 7;
ll p[maxn], inv[maxn];
ll ksm(ll x, ll y) {
   ll res = 1;
   while (y) {
       if (y & 1)
           res = res * x % mod;
       x = x * x % mod;
       y /= 2;
   }
   return res;
}
void init(int n) {
   p[0] = inv[0] = 1;
   for (int i = 1; i <= n; i++)
       p[i] = p[i - 1] * i % mod, inv[i] = ksm(p[i], mod - 2);
}
void add(ll &x, ll y) {
   x += y;
   if (x >= mod)
       x -= mod;
   if (x < 0)
       x += mod;
}
ll C(int n, int m) {
   return p[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
   init(2e5);
   int T;
   cin>>T;
   while (T--) {
       ll n, m, ans = 0;
       cin>>n>>m;
       int t = m / 2;
       for (int i = 0; i <= t; i++) {
           ll tmp = C(t, i) * ksm(m - 2 * i, n) % mod;
           add(ans, tmp);
       }
       ans = ans * ksm(ksm(2, t), mod - 2) % mod;
       cout<<ans<<'\n';
   }
}

F. Rhyme scheme

题意:构造一个长度为n合法字符串,第pos(pos < n)位的字符范围是A到A + pos - 1,最后一个字符范围是A到max(已经构造了的字符)+1,要求输出字典序第k小的合法字符串
解法:设dp[i][j]为已经构造了的最大字符为A+ i,有dp[i][j]个合法方案数构造剩下的 j 个位置的字符,下一位我可以填A到A + i:dp[i][j] += (i + 1)*dp[i][j -1],下一位我也可以填A + i +1:dp[i][j] += dp[i +1][j - 1],接下来我们从第一位开始从小到大构造,加入我要填A,那么我们用dp计算一下填A之后还剩多少合法方案数能填完,如果方案数x大于等于k,那么说明可以填A,否则说明不能填A,那么k-=x,然后假设取填B,以此类推
#include<bits/stdc++.h>
#define ll __int128
//#define ll long long
using namespace std;
ll d[27][27], k;
char s[30];
ll dfs(int i, int j) {
   if (d[i][j])
       return d[i][j];
   if (j == 1)
       return i + 2;
   d[i][j] = dfs(i, j - 1) * (i + 1) + dfs(i + 1, j - 1);
   return d[i][j];
}
int main() {
   int T, Case = 0;
   scanf("%d", &T);
   while (T--) {
       int n;
       scanf("%d%s", &n, s + 1);
       k = 0;
       for (int i = 1; s[i]; i++)
           k = k * 10 + s[i] - '0';
       printf("Case #%d: ", ++Case);
       int mx = 0;
       for (int i = 1; i <= n; i++) {
           if (i != n) {
               for (int j = 0; j < i; j++)
               if (j <= mx) {
                   if (k > dfs(mx, n - i))
                       k -= dfs(mx, n - i);
                   else {
                       printf("%c", 'A' + j);
                       break;
                   }
               }
               else {
                   if (k > dfs(j, n - i))
                       k -= dfs(j, n - i);
                   else {
                       printf("%c", 'A' + j);
                       mx = j;
                       break;
                   }
               }
           }
           else
               printf("%c", k - 1 + 'A');
       }
       puts("");
   }
}

G. Substring

题意: 题意:给了一个母串S, 每次循问给了一个模板串,问模板串在母 串中“匹配”了多少次?“匹配”的意思就是首字母和尾字母一样, 中间字母顺序可以换
解法:首先我们把字符串每个字符的数量去hash一下,就可以做到O(1)匹配,然后我们知道询问的串的不同长度种类数最多只有根号n,证明: 1 + 2 + 3 + 4 + . . x = ( 1 + x ) x / 2 = n = = > x = n 0.5 1+2+3+4+..x=(1+x)*x/2=n==>x=n^{0.5} ,那我们离线存好每个询问串hash值,我们枚举长度len,然后取出母串对应长度的所有子串hash值并排序,然后处理所有长度等于len的询问串,通过二分去查询每个询问串hash值在母串出现次数即可,复杂度nsqrt(n)logn
#include<bits/stdc++.h>
#define ull unsigned long long
#define pi pair<ull, int>
#define mk make_pair
using namespace std;
const int maxn = 1e5 + 5, base = 1e9 + 7;
ull p[400], h[maxn];
char s[maxn];
int L[maxn], N, m, a[maxn], ans[maxn];
vector<pi> G[maxn];
void gao() {
   scanf("%s", s + 1);
   N = strlen(s + 1);
   for (int i = 1; i <= N; i++)
       a[i] = s[i] - 'a';
}
void gaogao() {
   scanf("%d", &m);
   for (int i = 1; i <= m; i++) {
       scanf("%s", s + 1);
       int n = strlen(s + 1);
       L[i] = n;
       ull val = 0;
       for (int i = 2; i < n; i++)
           val += p[s[i] - 'a'];
       val += p[300] * (s[1] - 'a' + 1) + p[301] * (s[n] - 'a' + 1);
       G[n].push_back(mk(val, i));
   }
}
int main() {
   p[0] = 1;
   for (int i = 1; i < 400; i++)
       p[i] = p[i - 1] * base;
   int T;
   scanf("%d", &T);
   while (T--) {
       gao();
       gaogao();
       sort(L + 1, L + 1 + m);
       int sz = unique(L + 1, L + 1 + m) - L - 1;
       for (int i = 1; i <= sz; i++) {
           int n = L[i], cnt = 0;
           ull val = 0;
           for (int i = 1; i <= N; i++) {
               if (i >= n) {
                   val -= p[a[i - n + 1]];
                   ull tmp = val + p[300] * (a[i - n + 1] + 1) + p[301] * (a[i] + 1);
                   h[++cnt] = tmp;
               }
               val += p[a[i]];
           }
           sort(h + 1, h + 1 + cnt);
           for (auto tmp : G[n]) {
               int t2 = upper_bound(h + 1, h + 1 + cnt, tmp.first) - h;
               int t1 = lower_bound(h + 1, h + 1 + cnt, tmp.first) - h;
               ans[tmp.second] = t2 - t1;
           }
           G[n].clear();
       }
       for (int i = 1; i <= m; i++)
           printf("%d\n", ans[i]);
   }
}
发布了302 篇原创文章 · 获赞 98 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/ccsu_cat/article/details/100900748