2019 hdu多校 第一场

2019 Multi-University Training Contest 1

A

题意:给长度为 n 的序列染 4 种颜色,有 m 个限制,每个形如 \([l_i,r_i]\) 区间内恰好有 \(x_i\) 种颜色。问合法方案数。 \(n,m \le 100\)

key:dp

其实思路很简单,主要问题在于如何记录状态才能表示出这个限制。考虑一个区间内的颜色种数怎么表示:首先这个区间内必须已经染色,其次每种颜色只贡献 1 。后者可以用该颜色最后一次出现位置表示。

所以 \(f_{i,j,k,l}\) 表示四种颜色最后一次出现的位置。为了表示方便可以给他们排个序:为四种颜色最后一次出现位置排序后分比为 \(i, j, k, l, \ i<j<k<l\) 。这样每次就可以检查以 l 为结尾的所有区间是否合法了。 复杂度 \(O(n^4+n^3m)\)

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef long double LD;
typedef pair<int,int> pii;
typedef pair<LL,int> pli;
const int SZ = 8e6 + 10;
const int INF = 1e9 + 10;
const int mod = 998244353;
const LD eps = 1e-8;

LL read() {
    LL n = 0;
    char a = getchar();
    bool flag = 0;
    while(a > '9' || a < '0') { if(a == '-') flag = 1; a = getchar(); }
    while(a <= '9' && a >= '0') { n = n * 10 + a - '0',a = getchar(); }
    if(flag) n = -n;
    return n;
}

int f[2][110][110][110];
vector<pii> q[110];

bool check(int i,int j,int k,int t) {
    for(pii p : q[t]) {
        int l = p.first,x = p.second;
        //cout << l << " " << x << endl;
        if(l<=i) if(x!=4) return false; else continue;
        if(l<=j) if(x!=3) return false; else continue;
        if(l<=k) if(x!=2) return false; else continue;
        if(l<=t) if(x!=1) return false; else continue;
    }
    return true;
}

int main() {
    int T = read();
    while(T --) {
        int n = read(),m = read();
        for(int i = 1;i <= n;i ++) q[i].clear();
        for(int i = 1;i <= m;i ++) {
            int l = read(),r = read(),x = read();
            q[r].push_back(make_pair(l,x));
        }

       // cout << check(0,1,2,3) << endl;

        memset(f,0,sizeof f);
        f[0][0][0][0] = 1;
        for(int t = 0,cur = 0;t < n;t ++,cur^=1) {
            for(int k = 0;k <= max(t-1,0);k ++)
                for(int j = 0;j <= max(k-1,0);j ++)
                    for(int i = 0;i <= max(j-1,0);i ++)
                        f[cur^1][i][j][k] = 0;
            for(int k = 0;k <= max(t-1,0);k ++) {
                for(int j = 0;j <= max(k-1,0);j ++) {
                    for(int i = 0;i <= max(j-1,0);i ++) {
                        if(check(i,j,k,t)) {
                   //         printf("(%d,%d,%d,%d) = %d\n",i,j,k,t,f[cur][i][j][k]);
                            (f[cur^1][j][k][t] += f[cur][i][j][k]) %= mod;
                            (f[cur^1][i][k][t] += f[cur][i][j][k]) %= mod;
                            (f[cur^1][i][j][t] += f[cur][i][j][k]) %= mod;
                            (f[cur^1][i][j][k] += f[cur][i][j][k]) %= mod;
                        }
                      // else printf("!(%d,%d,%d,%d) = %d\n",i,j,k,t,f[cur][i][j][k]);
                    }
                }
            }
        }
        int ans = 0;
        for(int k = 0;k <= max(n-1,0);k ++) {
            for(int j = 0;j <= max(k-1,0);j ++) {
                for(int i = 0;i <= max(j-1,0);i ++) {
                    if(check(i,j,k,n)) {
                        (ans += f[n&1][i][j][k]) %= mod;
                    }
                }
            }
        }
        printf("%d\n",ans);
    }
}
/**
2
4 1
1 3 3
*/

B

题意:给一个序列,每次操作是尾部加入一个数、查询区间内的线性基。强制在线。 \(n,m \le 2*10^5\)

key:线性基

CF1100F

C

D

E

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef long double LD;
typedef pair<int,int> pii;
typedef pair<LL,int> pli;
const int SZ = 8e6 + 10;
const int INF = 1e9 + 10;
const int mod = 998244353;
const LD eps = 1e-8;

LL read() {
    LL n = 0;
    char a = getchar();
    bool flag = 0;
    while(a > '9' || a < '0') { if(a == '-') flag = 1; a = getchar(); }
    while(a <= '9' && a >= '0') { n = n * 10 + a - '0',a = getchar(); }
    if(flag) n = -n;
    return n;
}

vector<pii> g[SZ];

LL dist[SZ];
bool vis[SZ];
int n,m;

void dij(int s) {
    for(int i = 1;i <= n;i ++) dist[i] = 1e18,vis[i] = 0;
    dist[s] = 0;
    priority_queue<pli> q; q.push(make_pair(0,s));
    while(q.size()) {
        int u = q.top().second; q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(pii e : g[u]) {
            int v = e.first;
            if(dist[v] > dist[u] + e.second) {
                dist[v] = dist[u] + e.second;
                q.push(make_pair(-dist[v],v));
            }
        }
    }
}


struct edge {
    int f,t;
    LL d;
}l[SZ];

int head[SZ],nxt[SZ],tot = 1;

void build(int f,int t,LL d) {
    l[++ tot] = (edge){f,t,d};
    nxt[tot] = head[f];
    head[f] = tot;
}

void insert(int f,int t,LL d) {
    build(f,t,d); build(t,f,0);
}

int deep[SZ];

bool bfs(int s,int e) {
    for(int i = 1;i <= n;i ++) deep[i] = 0;
    deep[s] = 1;
    queue<int> q; q.push(s);
    while(q.size()) {
        int u = q.front(); q.pop();
        for(int i = head[u];i;i = nxt[i]) {
            int v = l[i].t;
            if(l[i].d && !deep[v]) {
                deep[v] = deep[u] + 1;
                q.push(v);
                if(v == e) return true;
            }
        }
    }
    return false;
}

LL dfs(int u,LL flow,int e) {
    if(u == e || flow == 0) return flow;
    LL ans = flow;
    for(int i = head[u];i;i = nxt[i]) {
        int v = l[i].t;
        if(l[i].d && deep[v] == deep[u] + 1) {
            LL f = dfs(v,min(ans,l[i].d),e);
            if(f > 0) {
                l[i].d -= f; l[i^1].d += f;
                ans -= f;
                if(ans == 0) break;
            }
            else deep[v] = 0;
        }
    }
    if(ans == flow) deep[u] = 0;
    return flow - ans;
}

LL dinic(int s,int e) {
    LL ans = 0;
    while(bfs(s,e)) {
        LL tmp = dfs(s,1e18,e);
        if(tmp == 0) break;
        ans += tmp;
    }
    return ans;
}

int ff[SZ],tt[SZ],dd[SZ];

int main() {
    int T = read();
    while(T --) {
        n = read(),m = read();
        for(int i = 1;i <= n;i ++) g[i].clear(),head[i] = 0; tot = 1;
        for(int i = 1;i <= m;i ++) {
            int x = read(),y = read(),z = read();
            g[x].push_back(make_pair(y,z));
            ff[i] = x; tt[i] = y; dd[i] = z;
        }
        dij(1);
        if(dist[n] == 1e18) { puts("0"); continue; }
        for(int i = 1;i <= m;i ++) {
            if(dist[tt[i]] == dist[ff[i]] + dd[i]) {
                insert(ff[i],tt[i],dd[i]);
            }
        }
        printf("%lld\n",dinic(1,n));
    }
}

F

题意:定义字符串生成操作:每次花费 p 的代价从尾部添加一个字符,或者 q 的代价从尾部添加一个当前字符串的子串。问生成给定字符串的最小代价。 \(\sum |S| \le 5*10^6\)

key:sam

显然是要 dp 的。容易得到一个 dp:添加一个字符,或者从之前某个位置。后者一定是当前字符串后缀是除去它的字符串的一个子串,具体地说,[j+1,i] 是 [1,j] 的一个子串。

这样的 j 可能有很多。由于随着长度的增大,代价也越来越大,所以肯定是维护最小的 j 。考虑已经维护了一个 j ,当添加了一个字符时,要么不动,要么在 sam 上走。所以复杂度是 \(O(n)\)

G

H

I

题意:给一个字符串,求长度恰好为 k ,每个字符出现次数在 \([l_i,r_i]\) 内的字典序最小子序列。 \(\sum |S| \le 3*10^5\)

key:贪心

没有出现次数的限制就是之前计蒜之道的那个题,这个题的限制更强一些。

考虑当前已经构造出了一个前缀,当前位置在 p ,考虑下一个字符应该选哪个。肯定是位置在 p 之后,字典序尽量小,并且选了它之后可以构造出符合要求的答案。

由于同种字母肯定选出现位置最靠前的,所以待选位置至多只有 O(m) 个,而做一次check也是 O(m) 的。所以复杂度是 \(O(nm^2)\) ,其中 m 是字符集,即26

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef long double LD;
typedef pair<int,int> pii;
typedef pair<LL,int> pli;
const int SZ = 1e6 + 10;
const int INF = 1e9 + 10;
const int mod = 998244353;
const LD eps = 1e-8;

LL read() {
    LL n = 0;
    char a = getchar();
    bool flag = 0;
    while(a > '9' || a < '0') { if(a == '-') flag = 1; a = getchar(); }
    while(a <= '9' && a >= '0') { n = n * 10 + a - '0',a = getchar(); }
    if(flag) n = -n;
    return n;
}

char a[SZ];
int k,n;
char S[SZ];
int top;
int b[SZ][27],L[27],R[27];

bool check(int a[27],int len,int b[27]) {
    int suml = 0,sumr = 0,sum = 0,s = 0;
    for(int i = 0;i < 26;i ++) {
        if(a[i] + b[i] < L[i]) return false;
        if(a[i] < L[i]) {
            sum += L[i] - a[i];
        }
        if(a[i] > R[i]) return false;
        s += min(b[i],R[i]-a[i]);
        suml += L[i];
        sumr += R[i];
    }
    if(s < len) return false;
    if(!(suml <= k && k <= sumr)) return false;
    if(sum > len) return false;
    return true;
}

char ans[SZ];

int main() {
    while(~scanf("%s%d",a,&k)) {
        for(int i = 0;i < 26;i ++) L[i] = read(),R[i] = read();
        int n = strlen(a);
        memset(b[n],0,sizeof b[n]);
        vector<int> g[27];
        for(int i = 0;i < n;i ++) g[a[i]-'a'].push_back(i);
        for(int i = n-1;i >= 0;i --) {
            for(int j = 0;j < 26;j ++) b[i][j] = b[i+1][j];
            b[i][a[i]-'a'] ++;
        }

        if(!check(b[n],k,b[0])) { puts("-1"); continue; }

        int nowid = -1;
        int h[27] = {},num[27] = {};
        for(int len = 0;len < k;len ++) {
            for(int i = 0;i < 26;i ++) {
                while(h[i] < g[i].size() && g[i][h[i]] <= nowid) h[i] ++;
                if(h[i] == g[i].size()) continue;
                int tmp[27] = {},id = g[i][h[i]];
                for(int j = 0;j < 26;j ++) tmp[j] = num[j];
                tmp[i] ++;
                if(check(tmp,k-len-1,b[id+1])) {
                   // printf("%d %d %d\n",len,i,id);
                    nowid = id;
                    ans[len] = i+'a';
                    num[i] ++;
                    break;
                }
            }
        }
        for(int i = 0;i < k;i ++) printf("%c",ans[i]); puts("");

    }
}

J

K

题意:求
\[ \sum_{1\le i \le n} \gcd(\lfloor \sqrt[3]{i} \rfloor,i), \ n \le 10^{21} \]
key:推导

主要是傻逼了,卡了奇怪的地方……

枚举三次根号后的那个值,\(\lfloor \sqrt[3]{n} \rfloor\) 特判。很容易推到
\[ \sum_{d\le m-1} \phi(d) \sum_{i\le (m-1)/d} \lfloor\frac{(id+1)^3-1}{d}\rfloor - \lfloor\frac{(id)^3-1}{d}\rfloor \]
然后后面那个看起来很难搞的东西只要展开就好了………………然后就得到了一个 \(O(n^{1/3})\) 的做法

#include<bits/stdc++.h>
using namespace std;

typedef __int128 LL;
typedef long double LD;
typedef pair<int,int> pii;
typedef pair<LL,int> pli;
const int SZ = 1e7 + 10;
const int INF = 1e9 + 10;
const int mod = 998244353;
const LD eps = 1e-8;

template <class T>
void read(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;
}

LL sqrt3(LL n) {
    LL l = 0,r = 1e7+10;
    while(r-l>1) {
        LL mid = (l+r) / 2;
        if(mid * mid * mid <= n) l = mid;
        else r = mid;
    }
    return l;
}

int phi[SZ];
LL sum[SZ];
int pri[SZ / 10];
bool vis[SZ];

void shai(int n) {
    phi[1] = 1;
    int tot = 0;
    for(int i = 2;i <= n;i ++) {
        if(!vis[i]) pri[++ tot] = i,phi[i] = i-1;
        for(int j = 1,m;j <= tot && (m=i*pri[j]) <= n;j ++) {
            vis[m] = 1;
            if(i%pri[j] == 0) {
                phi[m] = phi[i] * pri[j];
                break;
            }
            else {
                phi[m] = phi[i] * phi[pri[j]];
            }
        }
    }
}

LL ksm(LL a,LL b) {
    LL ans = 1;
    while(b) {
        if(b&1) ans = a * ans % mod;
        a = a *a % mod;
        b >>= 1;
    }
    return ans;
}

LL ni6 = ksm(6,mod-2);
LL ni2 = ksm(2,mod-2);

LL f2(LL n) {
    return n * (n+1) * (2*n+1) / 6;
}

LL f1(LL n) {
    return n * (n+1) / 2;
}

int baoli(int n) {
    int ans = 0;
    for(int i = 1;i <= n;i ++) {
        int x = (int)(pow(i,1.0/3)+1e-6);
        ans += __gcd(x,i);
    }
    return ans;
}

int main() {
    shai(1e7);
    int T; read(T);
    while(T --) {
        LL n; read(n);
        int m = sqrt3(n);
       // cout << m << endl;
        LL ans = 0;
        for(int d = 1;d < m;d ++) {
            LL tmp = 3*d*f2((m-1)/d)+ 3*f1((m-1)/d) + ((m-1)/d);
            ans += phi[d] * tmp;
        }
      //  cout << ans << endl;
        for(int d = 1;d <= m;d ++) {
            if(m % d == 0) {
                ans += phi[d] * (((n/d) - ((LL)m*m*m-1)/d));
            }
        }
        ans %= mod;
        cout << ans << endl;
    }
}

L

题意:给一个序列 a ,m 次操作,每次是求 $b_i = \sum_{j = i - k \cdot x} a_j \bmod 998244353 (0 \leq x, 1\leq j \leq i) $ ,然后用 \(b_i\) 替换 \(a_i\) 。求最终序列。 \(n \le 10^5, m \le 10^6, k \in {1,2,3}\)

key:ntt

题解里写的非常清楚…………

首先生成函数分析一下: \(\sum b_ix^i=(\sum x^{ki})(\sum a_ix^i)\) ,所以满足交换律,顺序没有关系。问题在于求 \((\sum x^{ki})^n=\sum {n-1+i \choose i} x^{ki}\) 。然后就做完了。

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef long double LD;
typedef pair<int,int> pii;
typedef pair<LL,int> pli;
const int SZ = 2e6 + 10;
const int INF = 1e9 + 10;
const int mod = 998244353;
const LD eps = 1e-8;

LL read() {
    LL n = 0;
    char a = getchar();
    bool flag = 0;
    while(a > '9' || a < '0') { if(a == '-') flag = 1; a = getchar(); }
    while(a <= '9' && a >= '0') { n = n * 10 + a - '0',a = getchar(); }
    if(flag) n = -n;
    return n;
}

LL ksm(LL a,LL b) {
    LL ans = 1;
    while(b) {
        if(b&1) ans = a * ans % mod;
        a = a *a % mod;
        b >>= 1;
    }
    return ans;
}

struct NTTranform {
    const int g = 3;
    void Transform(int *a,int n,int opt) {
        for(int i = 0,j = 0;i < n;i ++) {
            if(i < j) swap(a[i],a[j]);
            for(int k = n >> 1;(j ^= k) < k;k >>= 1);
        }
        for(int l = 2;l <= n;l *= 2) {
            int m = l / 2;
            int wn = ksm(g,(mod-1)/l);
            if(opt == -1) wn = ksm(wn,mod - 2);
            for(int *p = a;p != a + n;p += l) {
                for(int i = 0,w = 1;i < m;i ++,w=1ll*w*wn%mod) {
                    int t = 1ll * w * p[m + i] % mod;
                    p[m + i] = (p[i] - t + mod) % mod;
                    (p[i] += t) %= mod;
                }
            }
        }
    }
    void dft(int *a,const int n) {
        Transform(a,n,1);
    }
    void idft(int *a,const int n) {
        Transform(a,n,-1);
        int t = ksm(n,mod - 2);
        for(int i = 0;i < n;i ++) a[i] = 1ll * a[i] * t % mod;
    }
}ntt;

void multiply(int *a,int n,int *b,int m,int *ans) { /// need 4 times memory
    static int c1[SZ],c2[SZ];
    int len = 1;
    while(len < n + m) len *= 2;
    for(int i = 0;i < len;i ++) c1[i] = c2[i] = 0;
    for(int i = 0;i < n;i ++) c1[i] = a[i];
    for(int i = 0;i < m;i ++) c2[i] = b[i];
    ntt.dft(c1,len); ntt.dft(c2,len);
    for(int i = 0;i < len;i ++) c1[i] = 1ll * c1[i] * c2[i] % mod;
    ntt.idft(c1,len);
    for(int i = 0;i < n + m - 1;i ++) ans[i] = (c1[i] + mod) % mod;
}

int a[SZ],b[SZ],c[SZ],fac[SZ],invfac[SZ];

int C(int n,int m) {
    if(n<m) return 0;
    return 1ll * fac[n] * invfac[m] % mod * invfac[n-m] % mod;
}

int main() {
    fac[0] = 1;
    for(int i = 1;i <= 2e6;i ++) fac[i] = 1ll * i * fac[i-1] % mod;
    for(int i = 0;i <= 2e6;i ++) invfac[i] = ksm(fac[i],mod-2);
    int T = read();
    while(T --) {
        int n = read(),m = read();
        for(int i = 0;i < n;i ++) a[i] = read();
        int t[4] = {};
        for(int i = 1;i <= m;i ++) t[read()] ++;

        for(int k = 1;k <= 3;k ++) {
            if(t[k] == 0) continue;
            for(int i = 0;i < n;i ++) b[i] = c[i] = 0; b[0] = 1;
            int m = t[k];
            for(int i = 0;i < n;i += k) c[i] = C(i/k-1+m,i/k);
           // for(int i = 0;i < n;i ++) printf("%d ",b[i]); puts("");
           // for(int i = 0;i < n;i ++) printf("%d ",c[i]); puts("");
            multiply(b,n,c,n,b);
          //  for(int i = 0;i < n;i ++) printf("%d ",b[i]); puts("");
            multiply(b,n,a,n,a);
        }
        LL ans = 0;
       // for(int i = 0;i < n;i ++) printf("%d ",a[i]); puts("");
        for(int i = 0;i < n;i ++) {
            ans ^= (i+1ll) * a[i];
        }
        printf("%lld\n",ans);
    }
}

/**
2
5 2
3 2 2 4 1
2 2
*/

M

题意:二维平面上给两类点,问是否有一条直线把两类点分开。 \(n \le 100\)

key:计算几何

判断两个凸包是否相离。

猜你喜欢

转载自www.cnblogs.com/dqsssss/p/11235469.html
今日推荐