Lydsy12月月赛

A
题解的结论不会证呀…
所以我没做= =

B
考虑一种比较暴力的做法,dp线段树的节点
f[i][0/1],g[i][0/1]i//
可以发现因为是线段树的结构,区间长度相同的节点dp值是相同的
记忆化一下就过了

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int mod = 998244353;
inline void add(int &a,const int &b){a+=b;if(a>=mod)a-=mod;}

struct node
{
    ll f0,f1,f;
    int g0,g1;
    friend inline node operator +(const node x,const node y)
    {
        ll F0=x.f+y.f,F1=1ll+max(x.f0+y.f,x.f+y.f0),F=max(F0,F1);
        int G0=0,G1=0;
        if(x.f0+y.f0==F0) add(G0,(ll)x.g0*y.g0%mod);
        if(x.f0+y.f1==F0) add(G0,(ll)x.g0*y.g1%mod);
        if(x.f1+y.f0==F0) add(G0,(ll)x.g1*y.g0%mod);
        if(x.f1+y.f1==F0) add(G0,(ll)x.g1*y.g1%mod);

        if(x.f0+y.f0+1==F1) add(G1,(ll)x.g0*y.g0%mod*2ll%mod);
        if(x.f0+y.f1+1==F1) add(G1,(ll)x.g0*y.g1%mod);
        if(x.f1+y.f0+1==F1) add(G1,(ll)x.g1*y.g0%mod);

        return (node){F0,F1,F,G0,G1};
    }
};
map<ll,node>mp;

ll n;
node cal(ll l,ll r)
{
    if(l==r) return (node){0,0,0,1,0};
    ll len=r-l+1;
    if(mp.count(len)>0) return mp[len];
    ll mid=l+r>>1;
    node t1=cal(l,mid),t2=cal(mid+1ll,r);
    node re=t1+t2;
    mp[len]=re;
    return re;
}

int main()
{
    scanf("%lld",&n);
    node re=cal(1ll,n);
    int ans=re.g0;
    if(re.f1>re.f0) ans=re.g1;
    else if(re.f1==re.f0) add(ans,re.g1);
    printf("%lld %d\n",re.f,ans);

    return 0;
}

C
f[i][j][0/1]AiBj/
朴素的转移是 f[i][j][1]=ϵ(a[i]=b[j])i1x=0j1y=0f[x][y][0]ϵ(a[i]>a[x])
f[i][j][0] 的转移类似

但是这样是 O(n4K)
考虑优化转移

注意到 a[i]=b[j],a[x]=b[y] 这一条件
我们可以令 g[i][y][k]=ix=0f[x][y][k] ,比较的时候用 a[i]b[y] 比较
于是有新的dp式
f[i][j][0]=ϵ(a[i]=b[j])j1y=0g[i1][y][1]ϵ(a[i]<b[y])
复杂度 O(n3K) ,仍然不能通过

再次考虑优化
注意到 f[i]=g[i1] ,说明如果 g[i] 被拿去转移,转移的对象是确定的,就是 a[i+1] ,那 ϵ(a[i]<b[y]) 这个条件我们完全可以提前处理掉
h[i][j][k]=jy=0g[i1][y][k]ϵ(....)
转移的时候直接拿 h 转移到f,复杂度 O(n2K)

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 2100;
const int maxk = 10;
const int mod = 998244353;
inline void add(int &a,const int &b){a+=b;if(a>=mod)a-=mod;}

int n,m,K;
struct node
{
    int c[maxk];
    friend inline bool operator ==(const node x,const node y)
    {
        for(int j=1;j<=K;j++) if(x.c[j]!=y.c[j]) return false;
        return true;
    }
    friend inline bool operator <(const node x,const node y)
    {
        for(int j=1;j<=K;j++) if(x.c[j]>=y.c[j]) return false;
        return true;
    }
    friend inline bool operator >(const node x,const node y)
    {
        for(int j=1;j<=K;j++) if(x.c[j]<=y.c[j]) return false;
        return true;
    }
}a[maxn],b[maxn];

// 0 dec 1 up

int g[maxn][maxn][2],h[maxn][maxn][2];
int ans;

int main()
{
    scanf("%d",&K);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=K;j++) scanf("%d",&a[i].c[j]);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=K;j++) scanf("%d",&b[i].c[j]);
    }

    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++) for(int k=0;k<2;k++)
        {
            if(a[i]==b[j])
            {
                int t=h[i][j][!k];
                if(!k) add(t,1);
                add(g[i][j][k],t);
                add(ans,t);
            }
            add(g[i+1][j][k],g[i][j][k]);
            if(!k)
            {
                if(b[j]<a[i+1]) add(h[i+1][j+1][k],g[i][j][k]);
            }
            else
            {
                if(b[j]>a[i+1]) add(h[i+1][j+1][k],g[i][j][k]);
            }
            add(h[i][j+1][k],h[i][j][k]);
        }
    }
    printf("%d\n",ans);

    return 0;
}

D
一段内,每次交换相邻的,最终要求有序,最小交换次数是逆序对数
f[k][i]ik
f[k][i]=min(f[k1][j]+cost(j+1,i))
发现这个转移的决策点 j 单调不降
决策单调性dp有个经典的分治做法solve(pl,pr,l,r)
直接上,树状数组维护区间内逆序对数
复杂度 O(knlog2n)

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define lowbit(x) x&(-x)
#define inf 1e10
using namespace std;

const int maxn = 51000;
const int maxk = 12;

int n,k;
int a[maxn];

int s[maxn],L,R; ll sum;
inline void add(int x,int c){for(;x<=n;x+=lowbit(x))s[x]+=c;return;}
inline int query(int x){int re=0;for(;x;x-=lowbit(x))re+=s[x];return re;}
void move(int l,int r)
{
    while(R<r) sum+=R-L+1-query(a[++R]),add(a[R],1);
    while(L>l) sum+=query(a[--L]),add(a[L],1);
    while(R>r) sum-=R-L+1-query(a[R]),add(a[R--],-1);
    while(L<l) sum-=query(a[L]-1),add(a[L++],-1);
}
int nowk;
ll f[maxk][maxn];

void cal(int pl,int pr,int l,int r)
{
    if(l>r) return;
    int mid=l+r>>1,u=min(mid,pr);

    ll &temp=f[nowk][mid],cc; int ci;
    temp=inf;
    for(int i=pl;i<=u;i++) 
    {
        move(i,mid),cc=f[nowk-1][i-1]+sum;
        if(cc<temp) temp=cc,ci=i;
    }
    cal(pl,ci,l,mid-1),cal(ci,pr,mid+1,r);
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);

    L=1,R=0;
    for(int i=1;i<=n;i++) move(1,i),f[1][i]=sum;
    for(int i=2;i<=k;i++) 
        nowk=i,cal(1,n,1,n);

    printf("%lld\n",f[k][n]);

    return 0;
}

E
如果x能卖出y,连有向边 (x,y,cost[x])
每个点出度唯一,最终图肯定是个基环内向树的森林
可以先贪心每个点用入度最小的买,卖剩1个(如果不亏的话)
剩下的决策,对于每个联通块

先卖环上的肯定不会更亏
对于环上的点,枚举先卖哪一个,算这个联通块的贡献,取最大的
具体实现时反着建边建基环外向树会更好处理

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 1e9
using namespace std;

inline void up(int &a,const int &b){if(a<b)a=b;}
inline void down(int &a,const int &b){if(a>b)a=b;}
const int maxn = 210000;

int n;
int d[maxn],selfcir[maxn];
int s[maxn],buy[maxn],sold[maxn],c[maxn];
struct edge{int y,nex;}a[maxn]; int len,fir[maxn];
inline void ins(const int x,const int y){a[++len]=(edge){y,fir[x]};fir[x]=len;}

int t[maxn],tp; bool insta[maxn];
int dfn[maxn],low[maxn],id;
int bel[maxn],cnt;
int siz[maxn];
vector<int>V[maxn];

void tarjan(const int x)
{
    dfn[x]=low[x]=++id; insta[t[++tp]=x]=true;
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) 
    {
        if(!dfn[y]) tarjan(y),down(low[x],low[y]);
        else if(insta[y]) down(low[x],dfn[y]);
    }
    if(dfn[x]==low[x])
    {
        int la=0; ++cnt;
        while(la!=x)
        {
            insta[la=t[tp--]]=false;
            V[bel[la]=cnt].push_back(la);
            siz[cnt]++;
        }
    }
}
int nowi; 
ll ans;
void dfs(const int x)
{
    if(bel[x]!=nowi)
    {
        if(c[x]>0) ans+=c[x];
    }
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(bel[y]!=nowi)
        dfs(y);
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int t,x,y; scanf("%d%d%d%d",&t,&buy[i],&sold[i],&s[i]);
        d[t]++,ins(t,i);
        if(t==i) selfcir[i]=1;
    }
    for(int x=1;x<=n;x++) 
    {
        c[x]=inf;
        for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) down(c[x],buy[y]);
        c[x]=max(sold[x]-c[x],0);
        if(c[x]>0) ans+=(ll)(s[x]-1)*c[x];
    }
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);

    for(nowi=1;nowi<=cnt;nowi++)
    {
        if(siz[nowi]==1)
        {
            int x=V[nowi][0];
            if(selfcir[x]) dfs(x),ans+=c[x];
        }
        else
        {
            for(int j=0;j<V[nowi].size();j++)
                dfs(V[nowi][j]);
            int cc=inf;
            for(int j=0;j<V[nowi].size();j++)
            {
                int x=V[nowi][j];
                if(c[x]<=0) cc=0;
                else ans+=c[x];

                int mn=inf, nex=V[nowi][(j+1)%V[nowi].size()];
                for(int k=fir[nex],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(y!=x)
                    down(mn,buy[y]);
                mn=max(0,sold[nex]-mn);
                down(cc,c[nex]-mn);
            }
            ans-=cc;
        }
    }
    printf("%lld\n",ans);

    return 0;
}

F
看错题了qaq
因为对于每个子区间值域都连续,有两个性质
1:一个区间合法的充要条件是区间内任意两个相邻数绝对值差<=1
2:一个区间合法则他的任意一个子区间都合法

于是可以对于每个左端点处理出使他合法的区间右端点最远可以拉到哪
O(n+m)

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 1e9
using namespace std;

inline void read(int &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
const int maxn = 210000;

int n,m;
int a[maxn];
int R[maxn];

int main()
{
    read(n); read(m);
    for(int i=1;i<=n;i++) read(a[i]);
    a[n+1]=-1;
    for(int i=1;i<=n;i++)
    {
        int &temp=R[i];
        if(R[i-1]>=i) temp=R[i-1];
        else
        {
            temp=i;
            while(abs(a[temp+1]-a[temp])<=1) temp++;
        }
    }

    while(m--)
    {
        int l,r; read(l),read(r);
        puts(R[l]>=r?"YES":"NO");
    }

    return 0;
}

G
这是个暴力吧= =,加了一些剪枝…
没写…

H
边数是 n2 的,不能之间建边跑最短路
但事实上对于同一个点出边权值相同,可以放一起处理,跑 dijskral (我是不是拼错了…?)
一个点去更新时,会更新所有与他距离不超过 lim[x] 的,还未被更新过的点
建出点分树,因为树边全为1,对于每个点, BFS 出他子树里距离他的距离递增的队列
更新时对于他的每个祖先,不断取出队头直到队头距离超过 lim ,尝试更新

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

inline void read(int &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';

}
const int maxn = 310000;
const int maxd = 20;

int n,S;
struct edge{int y,nex;}a[maxn<<1]; int len,fir[maxn];
inline void ins(const int x,const int y){a[++len]=(edge){y,fir[x]};fir[x]=len;}

int fa[maxn],siz[maxn],msiz[maxn];
int v[maxn];
void dfs(const int x)
{
    siz[x]=1; msiz[x]=0;
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(!v[y]&&y!=fa[x])
    {
        fa[y]=x; dfs(y);
        siz[x]+=siz[y];
        msiz[x]=max(msiz[x],siz[y]);
    }
}
int sum;
int findroot(const int x)
{
    if((msiz[x]<<1)<=sum&&(sum-siz[x]<<1)<=sum) return x;
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(y!=fa[x]&&!v[y])
    {
        int rt=findroot(y);
        if(rt) return rt;
    }
    return 0;
}
int D[maxd][maxn];
queue<int>q[maxn],tmpq;
void build(const int root,int dep)
{
    tmpq.push(root); D[dep][root]=0;
    while(!tmpq.empty())
    {
        const int x=tmpq.front(); tmpq.pop();
        q[root].push(x);
        for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(!v[y]&&!D[dep][y])
            D[dep][y]=D[dep][x]+1,tmpq.push(y);
    }
}
int Fa[maxn];
void Divide(int x,int ff,const int dep)
{
    fa[x]=0; dfs(x); sum=siz[x];
    x=findroot(x);
    Fa[x]=ff; v[x]=dep;
    build(x,dep);

    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(!v[y])
        Divide(y,x,dep+1);
}

struct node
{
    ll x; int i;
    friend inline bool operator <(const node x,const node y){return x.x>y.x;}
};
priority_queue<node>Q;
int lim[maxn],cost[maxn];
ll dis[maxn];

void extend(int x)
{
    int t=x,Lim=lim[x],dep=v[x];
    while(x)
    {
        while(!q[x].empty())
        {
            int y=q[x].front();
            if(D[dep][y]>Lim) break;
            q[x].pop();
            if(dis[y]==-1) dis[y]=dis[t]+cost[t],Q.push((node){dis[y]+cost[y],y});
        }
        Lim=lim[t]-D[--dep][t];
        x=Fa[x];
    }
}
void Dij()
{
    for(int i=1;i<=n;i++) dis[i]=-1;
    dis[S]=0; Q.push((node){dis[S]+cost[S],S});
    while(!Q.empty())
    {
        const node now=Q.top(); Q.pop();
        extend(now.i);
    }
}

int main()
{
    read(n); read(S);
    for(int i=1;i<n;i++)
    {
        int x,y; read(x); read(y);
        ins(x,y); ins(y,x);
    }
    for(int i=1;i<=n;i++) read(lim[i]),read(cost[i]);

    Divide(1,0,1);
    Dij();
    for(int i=1;i<=n;i++) printf("%lld\n",dis[i]);

    return 0;
}

I
对于一个给定的串 S ,预处理一些东西后,f(S)可以用exkmp O(n)
发现本质不同的串个数是个集合划分,也就是 Bell(n)
Bell(12) 大概是400w个,直接爆搜出所有本质不同的串,算 f(S) ,乘个组合数
O(Bell(n)n)

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 15;
const int mod = 998244353;

int pw(int x,int k)
{
    int re=1;
    for(;k;k>>=1,x=(ll)x*x%mod) if(k&1)
        re=(ll)re*x%mod;
    return re;
}
int inv(int x){return pw(x,mod-2);}

int n,m;
int P[maxn][maxn],C[maxn],S[maxn];
int str[maxn],ans;
int tp;
int ex[maxn],mx,id;
void cal()
{
    mx=id=0;
    for(int i=2;i<=n;i++)
    {
        ex[i]=0;
        if(mx>=i) ex[i]=min(mx-i+1,ex[i-id+1]);
        while(str[i+ex[i]]==str[1+ex[i]]) ex[i]++;
        if(i+ex[i]-1>=mx) id=i,mx=i+ex[i]-1;
    }
    int cc=1,d=1;
    for(int i=1;i<=n;i++)
    {
        int r=1+i+ex[1+i]-1;
        if(r>=d) cc=(ll)cc*P[i][r-d+1]%mod,d=r+1;
    }
    ans+=(ll)cc*S[tp]%mod;
    if(ans>=mod)ans-=mod;
}
void dfs(int now)
{
    if(now==n+1) { cal();return; }
    str[now]=++tp; dfs(now+1); tp--;
    if(tp)
    {
        for(int i=1;i<=tp;i++) str[now]=i,dfs(now+1);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        P[i][0]=1;
        for(int j=1;j<=n;j++) P[i][j]=(ll)P[i][j-1]*i%mod;
    }
    C[0]=1;
    for(int i=1;i<=n;i++) C[i]=(ll)C[i-1]*(m-i+1)%mod*inv(i)%mod;
    S[0]=1;
    for(int i=1;i<=n;i++) S[i]=(ll)S[i-1]*(m-i+1)%mod;
    dfs(1);
    printf("%d\n",ans);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值