[冬令营模拟]wzj的题目#1

T1 少膜一个,T3 暴力写挂

强势 rank1 -> rank2

一场比赛两道线段树分治,给力

 

T1 password

给你 m 个禁止字符串,求长度为 n 的所有字符串中至少包含这些禁止字符串各一次的字符串数量

$n \leq 10^9,m \leq 4,\sum len \leq 50$

 

sol:容斥一下就变成了“m 个禁止字符串,一个都不出现的字符串数量”

这个可以转化成从 init 节点走 x 步走不到任意一个节点的方案数

矩阵加速即可

需要注意的是状态压缩被卡了,这种状压 + 矩乘可以用容斥来把矩阵变小

比赛的时候因为少膜了一下,挂了一个点

#include<bits/stdc++.h>
#define int long long
#define LL long long
using namespace std;
inline int read()
{
    int x = 0,f = 1;char ch = getchar();
    for(;!isdigit(ch);ch = getchar())if(ch == '-')f = -f;
    for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0';
    return x * f;
}

const int N = 150;
const int A = 10;
const int MOD = 998244353;

typedef vector<int> vec;
typedef vector<vec> mat;

mat mul(mat &A, mat &B)
{
    mat C(A.size(), vec(B[0].size()));
    for (int i = 0; i < A.size(); ++i)
    {
        for (int k = 0; k < B.size(); ++k)
        {
            for (int j = 0; j < B[0].size(); ++j)
            {
                C[i][j] = (C[i][j] + (LL)A[i][k] * B[k][j]) % MOD;
            }
        }
    }
    return C;
}

mat pow(mat A, int n)
{
    mat B(A.size(), vec(A.size()));
    for (int i = 0; i < A.size(); ++i)
        B[i][i] = 1;
    while (n > 0)
    {
        if (n & 1) B = mul(B, A);
        A = mul(A, A);
        n >>= 1;
    }
    return B;
}

struct ACAutomata
{

    int next[N][A], fail[N], end[N];
    int root, L;

    int idx(char ch)
    {
        return ch - '0';
    }
    int newNode()
    {
        for (int i = 0; i < A; ++i) next[L][i] = -1;
        end[L] = 0;
        return L++;
    }
    void init()
    {
        L = 0;
        root = newNode();
    }
    void insert(char buf[])
    {
        int len = strlen(buf);
        int now = root;
        for (int i = 0; i < len; ++i)
        {
            int ch = idx(buf[i]);
            if (next[now][ch] == -1) next[now][ch] = newNode();
            now = next[now][ch];
        }
        end[now]++;
    }
    void build()
    {
        queue<int> Q;
        fail[root] = root;
        for (int i = 0; i < A; ++i)
        {
            if (next[root][i] == -1)
            {
                next[root][i] = root;
            }
            else
            {
                fail[ next[root][i] ] = root;
                Q.push( next[root][i] );
            }
        }
        while (!Q.empty())
        {
            int now = Q.front();
            Q.pop();
            if (end[ fail[now] ]) end[now]++;
            for (int i = 0; i < A; ++i) 
            {
                if (next[now][i] == -1)
                {
                    next[now][i] = next[ fail[now] ][i];
                }
                else
                {
                    fail[ next[now][i] ] = next[ fail[now] ][i];
                    Q.push(next[now][i]);
                }
            }
        }
    }

    int query(int n)
    {
        mat F(L, vec(L));
        for (int i = 0; i < L; ++i)
        {
            for (int j = 0; j < L; ++j)
            {
                F[i][j] = 0;
            }
        }
        for (int i = 0; i < L; ++i)
        {
            for (int j = 0; j < A; ++j)
            {
                int nt = next[i][j];
                if (!end[nt]) F[i][nt]++;
            }
        }
        F = pow(F, n);
        int res = 0;
        for (int i = 0; i < L; ++i)
        {
            res = (res + F[0][i]) % MOD;
        }
        return res;
    }

} ac;
int m,n;
char buf[20][60];
int skr(int x,int t)
{
    int res = 1;
    while(t)
    {
        if(t & 1)res = res * x % MOD;
        x = x * x % MOD;
        t = t >> 1;
    }return res;
}
signed main()
{
    freopen("password.in","r",stdin);
    freopen("password.out","w",stdout);
    m = read(),n = read();
    int qwq = 0,qnq = skr(10,n);
    for(int i=1;i<=m;i++)cin>>buf[i];
    for(int SS=1;SS<=(1<<m)-1;SS++)
    {
        int flg = __builtin_popcount(SS),S = (flg & 1) ? 1 : -1;
        ac.init();
        memset(ac.next,-1,sizeof(ac.next));
        memset(ac.fail,0,sizeof(ac.fail));
        memset(ac.end,0,sizeof(ac.end));
        for(int i=0;i<m;i++)
            if(SS & (1 << i))ac.insert(buf[i+1]);
        ac.build();
        qwq += (S * ac.query(n));
    }
    cout<<((((qnq-qwq) % MOD) + MOD) % MOD)<<endl;
    return 0;
}
T1

 

T2 paint

APIO2014 特别行动队,但是所有东西都可以为负

额...就是你现在有 n 个人,每个人有权值,你可以把它们分成若干组,第 i 人只能跟 $[i-l,i+r]$ 这些人一组,一组的权值和为 $s$,一组的权值为 $ax^2+bx+c$ ,让你最大化权值

 

sol:因为一个人只能由 $[i-l,i+r]$ 转移过来,我们可以考虑这个人有贡献的区间,那显然是 $[i+l,min(i+r,n)]$ ,我们可以在线段树这个区间上打上这个人的 tag

然后我们按线段树的 dfs 序扫这个区间,因为这个人有贡献的区间一定在这个人右边,所以处理到这个区间的时候,所有打在上面的 tag 已经处理完毕了

于是我们可以把从这个叶子到根的 logn 层凸包并起来二分

然后就写完了。。

#include<bits/stdc++.h>
#define LL long long
#define DB long double
using namespace std;
inline int read()
{
    int x = 0,f = 1;char ch = getchar();
    for(;!isdigit(ch);ch = getchar())if(ch == '-')f = -f;
    for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0';
    return x * f;
}
const int maxn = 152600;
const DB inf = (DB)1.0 / 0.0;
int n,l,r;
LL a,b,c;
LL sum[maxn],f[maxn];
void force()
{
    for(int i=1;i<=n;i++)
    {
        for(int j=max(i-r,0);j<=min(i-l,n);j++)
            f[i] = max(f[i],f[j] + a * (sum[i] - sum[j]) * (sum[i] - sum[j]) + b * (sum[i] - sum[j]) + c);
    }
    cout<<f[n]<<endl;
}
struct point
{
    DB x,y;
    int id;
    point operator - (const point &b)const{return (point){x - b.x,y - b.y,id};}
    bool operator < (const point &b)const{return x < b.x;}
    DB operator * (const point &b)const{return x * b.y - y * b.x;}
}ps[maxn];
vector<int> Items[maxn << 2],ch[maxn << 2];
DB dp[maxn],Sum[maxn];
int q[maxn],top;
#define ls (x << 1)
#define rs ((x << 1) | 1)
void Insert(int x, int l, int r, int L, int R, int id)
{
    if(L <= l && r <= R){Items[x].push_back(id);return;}
    int mid = (l + r) >> 1;
    if(L <= mid) Insert(ls,l,mid,L,R,id);
    if(R > mid) Insert(rs,mid + 1,r,L,R,id);
}
inline DB trans(int frm,int targ){return (DB)dp[frm] + (DB)a * (Sum[targ] - Sum[frm]) * (Sum[targ] - Sum[frm]) + (DB)b * (Sum[targ] - Sum[frm]) + c;}
void CDQ(int x,int l,int r)
{
    int tp = 0;
    for(int i=0;i<Items[x].size();i++)
    {
        int to = Items[x][i];
        ps[++tp] = (point){Sum[to],dp[to] + (DB)a * Sum[to] * Sum[to] - (DB)b * Sum[to],to};
    }
    if(tp)
    {
        sort(ps + 1,ps + tp + 1);top = 0;
        q[++top] = 1;
        for(;q[1] <= tp && ps[q[1]].y == -inf;++q[1]);        
        for(int i=q[1]+1;i<=tp;i++)
        {
            if(ps[i].y == -inf) continue;
            for(;top > 1 && ((ps[q[top]] - ps[q[top-1]]) * (ps[i] - ps[q[top]])) >= 0;--top); 
            q[++top] = i;
        }
        if(q[1] > tp)top = 0;
        for(;top && ps[q[top]].y == -inf;--top);
        if(top)for(int i=1;i<=top;i++)ch[x].push_back(ps[q[i]].id);
    }
    if(l == r)
    {
        int now = x;
        while(now)
        {
            int nl = 0,nr = ch[now].size() - 1;
            while(nl < nr)
            {
                int md = (nl + nr) >> 1;
                if(md < (ch[now].size() - 1) && trans(ch[now][md],l) <= trans(ch[now][md+1],l))nl = md + 1;
                else nr = md;
            }
            if(ch[now].size())dp[l] = max(dp[l],trans(ch[now][nl],l));
            now = now >> 1;
        }
        return;
    }
    int mid = (l + r) >> 1;
    CDQ(ls,l,mid);CDQ(rs,mid + 1,r);
}
void solve()
{
    for(int i=1;i<=n;i++)Sum[i] = sum[i];
    for(int i=1;i<=n;i++)dp[i] = -inf;
    for(int i=0;i<=n;i++)Insert(1,0,n,i + l,min(i+r,n),i);
    //cout<<111<<endl;
    CDQ(1,0,n);LL ans = floor(dp[n]);
    printf("%lld\n",ans);
    //cout<<ret<<endl;
}
int main()
{
    freopen("paint.in","r",stdin);
    freopen("paint.out","w",stdout);
    n = read();a = read();b = read();c = read();l = read();r = read();
    for(int i=1;i<=n;i++)sum[i] = sum[i - 1] + read();
    //if(n <= 1000)force();
    //else
    solve();
}
T2

 

T3 route

树上找一条链,满足

1.链上最长边和最短边的差不超过 m

2.链上最短边 $\times$ 链上边权和 最大

$n,m \leq 2 \times 10^5$

sol:

如果没有 m 的限制,就是一个点分治

对于分治中心往下的每一条链,我们记一个信息 $(mi,su)$ 表示这条链上最小值为 $mi$ ,权值和为 $su$

按 $mi$ 的下标插入树状数组,每次查询即可

注意每次按儿子顺序正着做一遍,反着做一遍·

这样写可以 $O(nlog^2n)$ 拿到 80 分

 

或者我们用 LCT 维护子树信息,很好想但。。。不太可写

 

正解基于下面一个结论:如果树上一个连通块 u 的一条直径为 $u_i,u_j$ ,一个连通块 v 的一条直径为 $v_i,v_j$

则 $(u_i,v_i),(u_i,v_j),(u_j,v_i),(u_j,v_j)$ 其中至少一个为连通块 u,v 的并集的直径

于是对于 80 分我们可以按从大到小加入每条边然后直接统计答案

对于 100 分,除了加入,我们还有删除操作

这个时候我们可以线段树按时间分治,用一个可撤销的并查集来维护连通性

或者 LCT

#include<cstdio>
#include<cctype>
#include<queue>
#include<ctime>
#include<cstring>
#include<algorithm>
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define ren for(int i=first[x];i;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
typedef long long ll;
typedef pair<int,int> Pair;
const int maxn=160010;
struct Edge {
    int u,v,w;
    bool operator < (const Edge& ths) const {return w>ths.w;}
}E[maxn];
int n,lim,first[maxn],next[maxn<<1],dis[maxn<<1],to[maxn<<1],e;
void AddEdge(int u,int v,int w) {
    to[++e]=v;dis[e]=w;next[e]=first[u];first[u]=e;
    to[++e]=u;dis[e]=w;next[e]=first[v];first[v]=e;
}
ll mn[20][maxn*2],dep[maxn];
int pos[maxn*2],cnt;
void dfs(int x,int fa) {
     mn[0][++cnt]=dep[x];pos[x]=cnt;
     ren if(to[i]!=fa) {
         dep[to[i]]=dep[x]+dis[i];
         dfs(to[i],x);    
         mn[0][++cnt]=dep[x];
     }
}
int Log[maxn*2];
void init() {
     Log[0]=-1;rep(i,1,cnt) Log[i]=Log[i>>1]+1;
     for(int j=1;(1<<j)<=cnt;j++)
        for(int i=1;i+(1<<j)-1<=cnt;i++)
           mn[j][i]=min(mn[j-1][i],mn[j-1][i+(1<<j-1)]);     
}
ll query(int x,int y) {
    ll ans=dep[x]+dep[y];x=pos[x];y=pos[y];if(x>y) swap(x,y);
    int k=Log[y-x+1];
    return ans-2*min(mn[k][x],mn[k][y-(1<<k)+1]);    
}
Pair A[maxn];
Pair merge(Pair A,Pair B) {
    int x1=A.first,y1=A.second;
    int x2=B.first,y2=B.second;
    int x=x1,y=y1;
    if(query(x2,y2)>query(x,y)) x=x2,y=y2;
    if(query(x2,y1)>query(x,y)) x=x2,y=y1;
    if(query(x2,x1)>query(x,y)) x=x2,y=x1;
    if(query(y2,x1)>query(x,y)) x=y2,y=x1;
    if(query(y2,y1)>query(x,y)) x=y2,y=y1;
    return make_pair(x,y);
}
int pa[maxn],size[maxn];
int findset(int x) {return x==pa[x]?x:findset(pa[x]);}
int end[maxn];
struct Data {
    int x,y,pax,sizey;
    ll ansv;
    Pair datay;
}S[maxn];
int ToT;
ll maxlen,ans;
void link(int x,int y) {
    x=findset(x);y=findset(y);
    if(x==y) return;
    if(size[x]>size[y]) swap(x,y);
    S[++ToT]=(Data){x,y,pa[x],size[y],maxlen,A[y]};
    pa[x]=y;if(size[x]==size[y]) size[y]++;A[y]=merge(A[y],A[x]);
    maxlen=max(maxlen,query(A[y].first,A[y].second));
}
void restore(int begin) {
    while(ToT!=begin) {
        int x=S[ToT].x,y=S[ToT].y,pax=S[ToT].pax,sizey=S[ToT].sizey;
        maxlen=S[ToT].ansv;Pair z=S[ToT--].datay;
        pa[x]=pax;size[y]=sizey;A[y]=z;
    }
}
int ls[maxn<<1],rs[maxn<<1],rt;
void buildtree(int& o,int l,int r) {
    o=++ToT;if(l==r) return;
    int mid=l+r>>1;
    buildtree(ls[o],l,mid);buildtree(rs[o],mid+1,r);
}
int first2[maxn<<1],nxt2[maxn*20],id[maxn*20];
void AddMark(int x,int val) {
    id[++cnt]=val;nxt2[cnt]=first2[x];first2[x]=cnt;
}
void query(int o,int l,int r,int ql,int qr,int val) {
    if(ql<=l&&r<=qr) AddMark(o,val);
    else {
        int mid=l+r>>1;
        if(ql<=mid) query(ls[o],l,mid,ql,qr,val);
        if(qr>mid) query(rs[o],mid+1,r,ql,qr,val);
    }
}
void solve(int o,int l,int r) {
    int begin=ToT;
    for(int i=first2[o];i;i=nxt2[i]) link(E[id[i]].u,E[id[i]].v);
    if(l<r) {
        int mid=l+r>>1;
        solve(ls[o],l,mid);
        solve(rs[o],mid+1,r);
    }
    else ans=max(ans,maxlen*E[l].w);
    restore(begin);
}
int main() {
    freopen("route.in","r",stdin);
    freopen("route.out","w",stdout);
    n=read();lim=read();
    rep(i,1,n-1) {
        E[i].u=read();E[i].v=read();E[i].w=read();
        AddEdge(E[i].u,E[i].v,E[i].w);
    }
    buildtree(rt,1,n-1);ToT=0;
    dfs(1,0);init();cnt=0;
    sort(E+1,E+n);
    rep(i,1,n) pa[i]=i,A[i]=make_pair(i,i),size[i]=1;
    int j=n-1;
    dwn(i,n-1,1) {
        while(E[i].w-E[j].w>lim) j--;
        end[i]=j;query(rt,1,n-1,i,end[i],i);
    }
    solve(rt,1,n-1);
    printf("%lld\n",ans);
    return 0;
}
T3

 

posted @ 2018-11-26 20:37  探险家Mr.H  阅读(198)  评论(0编辑  收藏  举报