100道codeforces 2400 已完结

浅显的做法是二分+主席树,主席树维护区间里pre都落到了哪。复杂度nlogn^3
int tot,lc[2000000],rc[2000000],c[2000000],pre[100010],rt[100010],n;
int build(int x,int l,int r,int d)
{
    tot++;
    int t=tot;
    lc[t]=lc[x];
    rc[t]=rc[x];
    c[t]=c[x]+1;
    if(l==r)
        return t;
    int mid=(l+r)/2;
    if(d<=mid)
        lc[t]=build(lc[x],l,mid,d);
    else
        rc[t]=build(rc[x],mid+1,r,d);
    return t;
}
int ask(int x1,int x2,int l,int r,int tl,int tr)
{
    if(x1==0&&x2==0||tl<=l&&r<=tr)
        return c[x2]-c[x1];
    int mid=(l+r)/2;
    if(tr<=mid)
        return ask(lc[x1],lc[x2],l,mid,tl,tr);
    else if(tl>mid)
        return ask(rc[x1],rc[x2],mid+1,r,tl,tr);
    else
        return ask(lc[x1],lc[x2],l,mid,tl,tr)+ask(rc[x1],rc[x2],mid+1,r,tl,tr);
}
int ask(int x,int k)
{
    int l=x,r=n,mid;
    while(l+1<r)
    {
        mid=(l+r)/2;
        if(ask(rt[x-1],rt[mid],0,n,0,x-1)>k)//区间x到mid有多少个pre落在0到x-1
            r=mid;
        else
            l=mid;
    }
    if(ask(rt[x-1],rt[r],0,n,0,x-1)<=k)
        return r;
    return l;
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        rt[i]=build(rt[i-1],0,n,pre[x]);
        pre[x]=i;
    }
    for(int k=1;k<=n;k++)
    {
        int ans=0;
        for(int i=1;i<=n;i=ask(i,k)+1)
            ans++;
        printf("%d ",ans);
    }
}
因为调和级数+二分+主席树,总复杂度有三个log。我们考虑用主席树上二分省掉一个log
把主席树维护的东西反过来,维护pre为i的都从哪来的,就可以主席树上二分了。
因为我只会求第一次等于k的pos,所以求第一次等于k+1的pos再减一就得到了最后一个等于k的pos。
int tot,lc[2000000],rc[2000000],c[2000000],pre[100010],rt[100010],n;
queue<int>pos[100010];
int build(int x,int l,int r,int d)
{
    tot++;
    int t=tot;
    lc[t]=lc[x];
    rc[t]=rc[x];
    c[t]=c[x]+1;
    if(l==r)
        return t;
    int mid=(l+r)/2;
    if(d<=mid)
        lc[t]=build(lc[x],l,mid,d);
    else
        rc[t]=build(rc[x],mid+1,r,d);
    return t;
}
int ask(int x1,int x2,int l,int r,int tl,int tr)
{
    if(x1==0&&x2==0||tl<=l&&r<=tr)
        return c[x2]-c[x1];
    int mid=(l+r)/2;
    if(tr<=mid)
        return ask(lc[x1],lc[x2],l,mid,tl,tr);
    else if(tl>mid)
        return ask(rc[x1],rc[x2],mid+1,r,tl,tr);
    else
        return ask(lc[x1],lc[x2],l,mid,tl,tr)+ask(rc[x1],rc[x2],mid+1,r,tl,tr);
}
void build0(int x,int l,int r)
{
    if(l==r)
    {
        if(pos[0].size()&&l==pos[0].front())
            c[x]=1,pos[0].pop();
        return ;
    }
    int mid=(l+r)/2;
    tot++;
    lc[x]=tot;
    tot++;
    rc[x]=tot;
    build0(lc[x],l,mid);
    build0(rc[x],mid+1,r);
    c[x]=c[lc[x]]+c[rc[x]];
}
int askpos(int x,int l,int r,int k)
{
    if(l==r)
        return l;
    int mid=(l+r)/2;
    if(c[lc[x]]>=k)
        return askpos(lc[x],l,mid,k);
    else
        return askpos(rc[x],mid+1,r,k-c[lc[x]]);
}
int askc(int x,int l,int r,int d)
{
    if(r<=d)
        return c[x];
    int mid=(l+r)/2;
    if(d<=mid)
        return askc(lc[x],l,mid,d);
    else
        return c[lc[x]]+askc(rc[x],mid+1,r,d);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        
        pos[pre[x]].push(i);
        pre[x]=i;
    }
    tot++;
    build0(1,1,n+1);
    rt[0]=1;
    for(int i=1;i<=n;i++)
    {
        if(pos[i].size()==0)
        {
            rt[i]=rt[i-1];
            continue;
        }
        rt[i]=build(rt[i-1],1,n+1,pos[i].front());
        pos[i].pop();
        while(pos[i].size())
            rt[i]=build(rt[i],1,n+1,pos[i].front()),pos[i].pop();
    }
    for(int k=1;k<=n;k++)
    {
        int ans=0;
        for(int i=1;i<=n;)
        {
            ans++;
            i=askpos(rt[i-1],1,n+1,k+askc(rt[i-1],1,n+1,i));
        }
        printf("%d ",ans);
    }
}
786C

 

https://codeforces.com/problemset?order=BY_RATING_ASC&tags=2400-

 

考虑对于每次修改,先存起来。当修改的点超过100后再跑多源bfs更新dis数组,释放掉这些点。
对于每次询问,答案要么是当前存起来的点,可以枚举+lca。要么是被释放掉的点,可以用dis[x]得到。
复杂度m/siz*n(bfs更新dis)+m*siz*log(n) (lca)

int n,m,deep[100010],dis[100010],fa[100010][18];
vector<int>now,e[100010];
queue<int>q;
void dfs(int x)
{
    for(auto y:e[x])
    {
        if(y==fa[x][0])continue;
        fa[y][0]=x;
        deep[y]=deep[x]+1;
        dfs(y);
    }
}
int lca(int x,int y)
{
    int val=deep[x]+deep[y];
    if(deep[x]<deep[y])swap(x,y);
    for(int i=17;i>=0;i--)
        if(deep[fa[x][i]]>=deep[y])
            x=fa[x][i];
    if(x==y)
        return val-2*deep[x];
    for(int i=17;i>=0;i--)
        if(fa[x][i]!=fa[y][i])
            x=fa[x][i],y=fa[y][i];
    return val-2*deep[fa[x][0]];
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        e[x].push_back(y);
        e[y].push_back(x);
        dis[i]=0x3f3f3f3f;
    }
    dis[n]=0x3f3f3f3f;
    deep[1]=1;
    dfs(1);
    for(int i=1;i<=17;i++)
        for(int x=1;x<=n;x++)
            fa[x][i]=fa[fa[x][i-1]][i-1];
    now.push_back(1);
    for(;m;m--)
    {
        if(read()&1)
            now.push_back(read());
        else
        {
            int x=read();
            int ans=dis[x];
            for(auto y:now)
                ans=min(ans,lca(x,y));
            printf("%d\n",ans);
        }
        if(now.size()==100)
        {
            for(auto x:now)
            {
                if(dis[x])
                {
                    dis[x]=0;
                    q.push(x);
                }
            }
            now.clear();
            while(q.size())
            {
                int x=q.front();q.pop();
                for(auto y:e[x])
                {
                    if(dis[y]>dis[x]+1)
                    {
                        dis[y]=dis[x]+1;
                        q.push(y);
                    }
                }
            }
        }
    }
}
342E
众所周知,大小为n的子树里不同出现次数只有根号n个
所以考虑dsu,用sum维护有多少种颜色出现了i次,用o维护颜色i出现了几次。对于每个k,用o.size得到总颜色数,再枚举sum里的元素,减去不合法的即可。复杂度nlog^2(dsu合并)+m*sqrt(n)*log(每次询问)
map<int,int>o[100010],sum[100010];
int c[100010],n,m,ans[100010];
vector<int>e[100010];
vector<pair<int,int>>ask[100010];
void merge(map<int,int> &sum1,map<int,int>&o1,map<int,int>&sum2,map<int,int>&o2)
{
    if(o2.size()>o1.size())
    {
        swap(sum1,sum2);
        swap(o1,o2);
    }
    for(auto x:o2)
    {
        if(o1[x.first])//有的话
        {
            sum1[o1[x.first]]--;
            if(sum1[o1[x.first]]==0)
                sum1.erase(o1[x.first]);
            o1[x.first]+=x.second;
            sum1[o1[x.first]]++;
        }
        else
        {
            o1[x.first]=x.second;
            sum1[o1[x.first]]++;
        }
    }
}
void dfs(int x,int fa)
{
    sum[x][1]=1;//有一种颜色=1
    o[x][c[x]]=1;//c[x]在x的子树里出现了x次
    for(auto y:e[x])
    {
        if(y==fa)continue;
        dfs(y,x);
        merge(sum[x],o[x],sum[y],o[y]);
    }
    for(auto it:ask[x])
    {
        ans[it.second]=o[x].size();
        for(auto y:sum[x])
        {
            if(y.first<it.first)
                ans[it.second]-=y.second;
            else
                break;
        }
    }
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
        c[i]=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        e[x].push_back(y);
        e[y].push_back(x);
    }
    for(int i=1;i<=m;i++)
    {
        int x=read();
        ask[x].push_back({read(),i});
    }
    dfs(1,0);
    for(int i=1;i<=m;i++)
        printf("%d\n",ans[i]);
}
375D
不会做
看了看别人写的,发现这样写的话是对的。。。
int n;
ll ans;
priority_queue<int,vector<int>,greater<>>q;
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        q.push(x);
        if(x>q.top())
        {
            ans=ans+x-q.top();
            q.pop();
            q.push(x);
        }
    }
    cout<<ans;
}
865D

 449D

第一步,考虑得到f[x]表示有多少个a[i]&x=x

 

 f[x]可以看做是ai刚开始位于自己的位置,开始跑动态规划。最终的f[x]是有多少点“汇入”了x号点。

注意到15到3有两种路径:15->7->3和15->11->3。为了避免重复,规定dp时从小到大枚举1<<j,那么路径就只会是先减去小的1<<j,再减去大的1<<j也就是先有f[11]+=f[15],再有f[3]+=f[11]

第二步就好说了,大胆容斥一下。

(update:原来这个叫高维前缀和)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int read()
{
    int x;scanf("%d",&x);return x;
}
ll mod=1000000007;
int n,POW[3000010],f[3000010],ans;
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    POW[0]=1;
    for(int i=1;i<=n;i++)
    {
        f[read()]++;
        POW[i]=POW[i-1]*2%mod;
    }
    for(int j=0;j<=20;j++)
        for(int i=0;i<(1<<20);i++)
            if((i&(1<<j))==0)
                f[i]=(f[i]+f[i+(1<<j)])%mod;
    for(int i=0;i<(1<<20);i++)
    {
        int now=1;
        for(int j=0;j<=20;j++)
            if(i&(1<<j))
                now=now*-1;
        ans=ans+now*POW[f[i]];
        ans=(ans%mod+mod)%mod;
    }
    cout<<ans;
}
449D
斜率优化dp
f[i]=max(f[j]+(x[i]-x[j])*y[i])-a[i])
f[i]+a[i]-x[i]*y[i]=max(f[j]-x[j]*y[i])
是在求最大的截距,用vector维护凸包即可
struct node
{
    ll x,y,a;
    friend bool operator <(node a,node b)
    {
        return a.x<b.x;
    }
}o[1000010];
ll n,ans,f[1000010];
struct ttt
{
    ll x,f;
};
vector<ttt>q;
int ask(ttt a,ttt b,ttt c)
{
    return 1.0*(c.f-b.f)/(c.x-b.x)>=1.0*(b.f-a.f)/(b.x-a.x);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
    {
        o[i].x=read();
        o[i].y=read();
        o[i].a=read();
    }
    sort(o+1,o+1+n);
    int now=0;
    q.push_back({0,0});
    for(int i=1;i<=n;i++)
    {
        while(now+1<q.size()&&q[now].f-o[i].y*q[now].x<q[now+1].f-o[i].y*q[now+1].x)
            now++;
        f[i]=q[now].f-o[i].y*q[now].x-o[i].a+o[i].x*o[i].y;
        while(q.size()>=2&&ask(q[q.size()-2],q[q.size()-1],{o[i].x,f[i]}))
            q.pop_back();
 
        q.push_back({o[i].x,f[i]});
        now=min(now,(int)q.size()-1);
        ans=max(ans,f[i]);
    }
    cout<<ans;
}
1083E
考虑离线地处理询问
用线段树维护只有1-o[i].r这些数字时,对于每个位置,如果是最后一次出现,线段树的值为上一次出现的下标,否则为inf
那么对于区间o[i].l,o[i].r,如果区间最小值大于等于o[i].l,证明区间里没有只出现一次的数字。否则找到最小值在哪里,他的值为合法的答案。
int n,a[500010],ans[500010],pos[500010];
pair<int,int>minn[4000010];
struct node
{
    int l,r,i;
}o[500010];
bool r_(node a,node b)
{
    return a.r<b.r;
}
void add(int x,int l,int r,int d,int v)
{
    if(l==r)
    {
        minn[x]={v,d};
        return ;
    }
    int mid=(l+r)/2;
    if(d<=mid)
        add(x*2,l,mid,d,v);
    else
        add(x*2+1,mid+1,r,d,v);
    minn[x]=min(minn[x*2],minn[x*2+1]);
}
pair<int,int> ask(int x,int l,int r,int tl,int tr)
{
    if(tl<=l&&r<=tr)
        return minn[x];
    int mid=(l+r)/2;
    if(tr<=mid)
        return ask(x*2,l,mid,tl,tr);
    else if(tl>mid)
        return ask(x*2+1,mid+1,r,tl,tr);
    else
        return min(ask(x*2,l,mid,tl,tr),ask(x*2+1,mid+1,r,tl,tr));
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    int q=read();
    for(int i=1;i<=q;i++)
    {
        o[i].l=read();
        o[i].r=read();
        o[i].i=i;
    }
    sort(o+1,o+1+q,r_);
    for(int i=1;i<=q;i++)
    {
        for(int j=o[i-1].r+1;j<=o[i].r;j++)
        {
            if(pos[a[j]])
                add(1,1,n,pos[a[j]],n+1);
            add(1,1,n,j,pos[a[j]]);
            pos[a[j]]=j;
        }
        pair<int,int>t=ask(1,1,n,o[i].l,o[i].r);
        if(t.first<o[i].l)//last,pos 寻找最小last
            ans[o[i].i]=a[t.second];
    }
        
    for(int i=1;i<=q;i++)
        cout<<ans[i]<<'\n';
}
1000F
数组开nlogn
int n,a[500010],pos[500010],rt[500010],tot;
int lc[20000010],rc[20000010];
pair<int,int>minn[20000010];
pair<int,int> ask(int x,int l,int r,int tl,int tr)
{
    if(tl<=l&&r<=tr)
        return minn[x];
    int mid=(l+r)/2;
    if(tr<=mid)
        return ask(lc[x],l,mid,tl,tr);
    else if(tl>mid)
        return ask(rc[x],mid+1,r,tl,tr);
    else
        return min(ask(lc[x],l,mid,tl,tr),ask(rc[x],mid+1,r,tl,tr));
}
int built(int now,int l,int r,int d,int v)
{
    int t=++tot;
    lc[t]=lc[now];rc[t]=rc[now];
    if(l==r)
    {
        minn[t]={v,l};
        return t;
    }
    int mid=(l+r)/2;
    if(d<=mid)
        lc[t]=built(lc[now],l,mid,d,v);
    else
        rc[t]=built(rc[now],mid+1,r,d,v);
    minn[t]=min(minn[lc[t]],minn[rc[t]]);
    return t;
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    minn[0]={n+1,0};
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        if(pos[a[i]])
        {
            rt[i]=built(rt[i-1],1,n,pos[a[i]],n+1);
            rt[i]=built(rt[i],1,n,i,pos[a[i]]);
        }
        else
            rt[i]=built(rt[i-1],1,n,i,pos[a[i]]);        
        pos[a[i]]=i;
    }
    for(int q=read();q;q--)
    {
        int l=read(),r=read();
        pair<int,int>t=ask(rt[r],1,n,l,r);
        if(t.first<l)
            printf("%d\n",a[t.second]);
        else
            printf("0\n");
    }
}
1000F 主席树版本
考虑线段树
对于线段树上的节点,维护sum表示区间和,f1f2表示这个区间加上了以f1f2开头的"斐波那契数列"。
众所周知,Σfi=f[n+2]-f[2]那么在pushdown时,把f1f2下传到左儿子区间可以直接加过去,传到右儿子区间需要算一下新的f1f2。有了左右儿子区间的f1f2和区间长度,就可以算左右儿子区间加的sum了。
已知f1f2,求第n项是经典的矩阵快速幂,但是会T10。我看了网上题解后得知可以预处理出斐波那契数列每一项,然后O(1)询问第n项。

int sum[1200010],f1[1200010],f2[1200010],f[300010];
int n,m,mod=1000000009;
// struct node
// {
//     int v[3][3];
//     friend node operator *(node a,node b)
//     {
//         node c;
//         for(int i=1;i<=2;i++)
//         {
//             for(int j=1;j<=2;j++)
//             {
//                 c.v[i][j]=0;
//                 for(int k=1;k<=2;k++)
//                     c.v[i][j]=(c.v[i][j]+1ll*a.v[i][k]*b.v[k][j]%mod)%mod;
//             }
//         }
//         return c;
//     }
// };
// int askf(int x,int F1,int F2)
// {
//     x--;
//     node a,b;
//     a.v[1][1]=0;
//     a.v[1][2]=a.v[2][1]=a.v[2][2]=1;
//     b.v[1][1]=b.v[2][2]=1;
//     b.v[1][2]=b.v[2][1]=0;
//     while(x)
//     {
//         if(x&1)
//             b=b*a;
//         a=a*a;
//         x=x/2;
//     }
//     return (1ll*F1*b.v[1][1]+1ll*F2*b.v[1][2])%mod;
// }

int askf(int x,int F1,int F2)
{
    if(x==1)
        return F1;
    return (1ll*F1*f[x-2]+1ll*F2*f[x-1])%mod;
}
void build(int x,int l,int r)
{
    if(l==r)
    {
        sum[x]=read();
        return ;
    }
    int mid=(l+r)/2;
    build(x*2,l,mid);
    build(x*2+1,mid+1,r);
    sum[x]=(sum[x*2]+sum[x*2+1])%mod;
}
void pushdown(int x,int l,int r)
{
    if(!f1[x]&&!f2[x])
        return ;
    int mid=(l+r)/2;
    f1[x*2]=(f1[x*2]+f1[x])%mod;
    f2[x*2]=(f2[x*2]+f2[x])%mod;
    int f1r=askf(mid-l+2,f1[x],f2[x]);
    int f2r=askf(mid-l+3,f1[x],f2[x]);
    f1[x*2+1]=(f1[x*2+1]+f1r)%mod;
    f2[x*2+1]=(f2[x*2+1]+f2r)%mod;
    sum[x*2]=((sum[x*2]+f2r-f2[x])%mod+mod)%mod;
    sum[x*2+1]=((sum[x*2+1]+askf(r-mid+2,f1r,f2r)-f2r)%mod+mod)%mod;
    f1[x]=f2[x]=0;
}
void add(int x,int l,int r,int tl,int tr)
{
    if(tl<=l&&r<=tr)
    {
        sum[x]=((sum[x]+askf(r-l+3,f[l-tl+1],f[l-tl+2])-f[l-tl+2])%mod+mod)%mod;
        f1[x]=(f1[x]+f[l-tl+1])%mod;
        f2[x]=(f2[x]+f[l-tl+2])%mod;
        return ;
    }
    pushdown(x,l,r);
    int mid=(l+r)/2;
    if(tl<=mid)
        add(x*2,l,mid,tl,tr);
    if(tr>mid)
        add(x*2+1,mid+1,r,tl,tr);
    sum[x]=(sum[x*2]+sum[x*2+1])%mod;
}
int asksum(int x,int l,int r,int tl,int tr)
{
    if(tl<=l&&r<=tr)
        return sum[x];
    pushdown(x,l,r);
    int mid=(l+r)/2;
    if(tr<=mid)
        return asksum(x*2,l,mid,tl,tr);
    else if(tl>mid)
        return asksum(x*2+1,mid+1,r,tl,tr);
    else
        return (asksum(x*2,l,mid,tl,tr)+asksum(x*2+1,mid+1,r,tl,tr))%mod;

}
int main()
{
    // freopen("1.in","r",stdin);
    f[1]=f[2]=1;
    for(int i=3;i<=300005;i++)
        f[i]=(f[i-1]+f[i-2])%mod;
    n=read();m=read();
    build(1,1,n);
    for(;m;m--)
    {
        if(read()&1)
        {
            int tl=read(),tr=read();
            add(1,1,n,tl,tr);
        }
        else
        {
            int tl=read(),tr=read();
            printf("%d\n",asksum(1,1,n,tl,tr));
        }
    }
}
446C
分层图
d[x][0/1][0/1]表示到x点,是否减过e[i].x,是否加过e[i].w
那么00状态可以转移到00,01,10,11
10状态可以转移到10,11
01状态可以转移到01,11
11状态可以转移到11
开始跑最短路即可
struct qy
{
    ll d;
    int x,v1,v2;
    friend bool operator <(qy a,qy b)
    {
        return a.d>b.d;
    }
};
priority_queue<qy>q;
int n,m,v[200010][2][2];
ll d[200010][2][2];
vector<pair<int,int>>e[200010];
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    memset(d,0x3f,sizeof(d));
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read(),w=read();
        e[x].push_back({y,w});
        e[y].push_back({x,w});
    }
    d[1][0][0]=0;
    q.push({0,1,0,0});
    while(q.size())
    {
        int x=q.top().x,v1=q.top().v1,v2=q.top().v2;
        q.pop();
        if(v[x][v1][v2])continue;
        v[x][v1][v2]=1;
        for(auto t:e[x])
        {
            if(d[t.first][v1][v2]>d[x][v1][v2]+t.second)
            {
                d[t.first][v1][v2]=d[x][v1][v2]+t.second;
                q.push({d[t.first][v1][v2],t.first,v1,v2});
            }
        }
        if(!v1&&!v2)
        {
            for(auto t:e[x])
            {
                if(d[t.first][1][1]>d[x][0][0]+t.second)
                {
                    d[t.first][1][1]=d[x][0][0]+t.second;
                    q.push({d[t.first][1][1],t.first,1,1});
                }
            }
        }
        if(!v1)
        {
            for(auto t:e[x])
            {
                if(d[t.first][1][v2]>d[x][0][v2])
                {
                    d[t.first][1][v2]=d[x][0][v2];
                    q.push({d[t.first][1][v2],t.first,1,v2});
                }
            }   
        }
        if(!v2)
        {
            for(auto t:e[x])
            {
                if(d[t.first][v1][1]>d[x][v1][0]+2*t.second)
                {
                    d[t.first][v1][1]=d[x][v1][0]+2*t.second;
                    q.push({d[t.first][v1][1],t.first,v1,1});
                }
            }   
        }
    }
    for(int i=2;i<=n;i++)
        printf("%lld ",d[i][1][1]);
}
1473E
又是斜率优化dp
f[i][j]=min(f[i][j],f[pre][j-1]+o[i]*(i-pre)-sum[i]+sum[pre]);
变化一下
f[i][j]-o[i]*i+sum[i]=min(f[pre][j-1]+sum[pre]-pre*o[i])
ll n,m,p;
ll d[100010],o[100010],sum[100010],f[100010][110];
ll now[110];
struct ttt
{
    ll x,f;
};
vector<ttt>q[110];
int ask(ttt a,ttt b,ttt c)
{
    return 1.0*(c.f-b.f)/(c.x-b.x)<=1.0*(b.f-a.f)/(b.x-a.x);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();p=read();
    for(int i=2;i<=n;i++)
        d[i]=d[i-1]+read();//和一号的距离
    for(int i=1;i<=m;i++)
    {
        int h=read();
        int t=read();
        o[i]=t-d[h];//这个时间出发的恰好能接到
    }
    sort(o+1,o+1+m);
    for(int i=1;i<=m;i++)
        sum[i]=sum[i-1]+o[i];//前缀和
    memset(f,0x3f,sizeof(f));
    f[0][0]=0;
    q[0].push_back({0,0});
    for(int j=1;j<=p;j++)
        for(int i=1;i<=m;i++)
        {   
            while(now[j-1]+1<q[j-1].size()&&q[j-1][now[j-1]].f-o[i]*q[j-1][now[j-1]].x>q[j-1][now[j-1]+1].f-o[i]*q[j-1][now[j-1]+1].x)
                now[j-1]++;
            f[i][j]=q[j-1][now[j-1]].f-o[i]*q[j-1][now[j-1]].x+o[i]*i-sum[i];
            while(q[j].size()>=2&&ask(q[j][q[j].size()-2],q[j][q[j].size()-1],{i,f[i][j]+sum[i]}))
                q[j].pop_back();
            q[j].push_back({i,f[i][j]+sum[i]});
        }
    cout<<f[m][p];

}
311B
考虑线段树
如果区间颜色一样就直接修改,打懒标记
否则下传懒标记,进入下一层
复杂度不太懂啊
int now[400010];
ll sum[400010],lazy[400010];
void build(int x,int l,int r)
{
    if(l==r)
    {
        now[x]=l;
        return ;
    }
    int mid=(l+r)/2;
    build(x*2,l,mid);
    build(x*2+1,mid+1,r);
}
void pushdown(int x,int l,int r)
{
    int mid=(l+r)/2;
    sum[x*2]+=1ll*(mid-l+1)*lazy[x];
    sum[x*2+1]+=1ll*(r-mid)*lazy[x];
    lazy[x*2]+=lazy[x];
    lazy[x*2+1]+=lazy[x];
    lazy[x]=0;
    now[x*2]=now[x*2+1]=now[x];
}
void add(int x,int l,int r,int tl,int tr,int v)
{
    if(tl<=l&&r<=tr&&now[x])//是一个颜色
    {
        sum[x]+=1ll*(r-l+1)*abs(v-now[x]);
        lazy[x]+=abs(v-now[x]);
        now[x]=v;
        return ;
    }
    if(lazy[x])
        pushdown(x,l,r);
    int mid=(l+r)/2;
    if(tl<=mid)
        add(x*2,l,mid,tl,tr,v);
    if(tr>mid)
        add(x*2+1,mid+1,r,tl,tr,v);
    sum[x]=sum[x*2]+sum[x*2+1];
    if(now[x*2]==now[x*2+1])
        now[x]=now[x*2];
    else
        now[x]=0;
}
ll ask(int x,int l,int r,int tl,int tr)
{
    if(tl<=l&&r<=tr)
        return sum[x];
    if(lazy[x])
        pushdown(x,l,r);
    int mid=(l+r)/2;
    ll t=0;
    if(tl<=mid)
        t+=ask(x*2,l,mid,tl,tr);
    if(tr>mid)
        t+=ask(x*2+1,mid+1,r,tl,tr);
    return t;
}
int main()
{
    // freopen("1.in","r",stdin);
    int n=read();
    build(1,1,n);
    for(int m=read();m;m--)
    {
        if(read()&1)
        {
            int l=read(),r=read();
            add(1,1,n,l,r,read());
        }
        else
        {
            int l=read();
            printf("%lld\n",ask(1,1,n,l,read()));
        }
    }
}
444C
启发式合并,大胆开stl,map套set。
struct node
{
    int k,i;
};
vector<node>ask[100010];
string s[100010];
vector<int>e[100010];
int ans[100010],d[100010];
map<int,set<string>>o[100010];
void merge(int x,int y)
{
    if(o[x].size()<o[y].size())
        swap(o[x],o[y]);
    for(auto it:o[y])
        for(auto i:it.second)
            o[x][it.first].insert(i);
}
void dfs(int x)
{
    for(auto y:e[x])
    {
        d[y]=d[x]+1;
        dfs(y);
        merge(x,y);
    }
    o[x][d[x]].insert(s[x]);
    for(auto i:ask[x])
        ans[i.i]=o[x][i.k+d[x]].size();
}
int main()
{
    // freopen("1.in","r",stdin);
    int n=read();
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
        e[read()].push_back(i);
    }
    int m=read();
    for(int i=1,v;i<=m;i++)
    {
        cin>>v;
        ask[v].push_back({read(),i});
    }
    dfs(0);
    for(int i=1;i<=m;i++)
        printf("%d\n",ans[i]);
}
246E
好简单的题
线段树维护区间里4,7,47,74的最长子序列长度即可。
int n,m,f44[4000010],f77[4000010],f47[4000010],f74[4000010],lazy[4000010];
char s[1000010];
void build(int x,int l,int r)
{
    if(l==r)
    {
        if(s[l]=='4')
            f44[x]=f74[x]=1;
        else
            f77[x]=f47[x]=1;
        return ;
    }
    int mid=(l+r)/2;
    build(x*2,l,mid);
    build(x*2+1,mid+1,r);
    f44[x]=f44[x*2]+f44[x*2+1];
    f77[x]=f77[x*2]+f77[x*2+1];
    f47[x]=max(f44[x*2]+f47[x*2+1],max(f44[x*2]+f77[x*2+1],f47[x*2]+f77[x*2+1]));
    f74[x]=max(f77[x*2]+f74[x*2+1],max(f74[x*2]+f44[x*2+1],f44[x*2]+f44[x*2+1]));
}
void pushdown(int x)
{
    swap(f44[x*2],f77[x*2]);
    swap(f47[x*2],f74[x*2]);
    swap(f44[x*2+1],f77[x*2+1]);
    swap(f47[x*2+1],f74[x*2+1]);
    lazy[x*2]^=1;
    lazy[x*2+1]^=1;
    lazy[x]=0;
}
void add(int x,int l,int r,int tl,int tr)
{
    if(tl<=l&&r<=tr)
    {
        swap(f44[x],f77[x]);
        swap(f47[x],f74[x]);
        lazy[x]^=1;
        return ;
    }
    if(lazy[x])
        pushdown(x);
    int mid=(l+r)/2;
    if(tl<=mid)
        add(x*2,l,mid,tl,tr);
    if(tr>mid)
        add(x*2+1,mid+1,r,tl,tr);
    f44[x]=f44[x*2]+f44[x*2+1];
    f77[x]=f77[x*2]+f77[x*2+1];
    f47[x]=max(f44[x*2]+f47[x*2+1],max(f44[x*2]+f77[x*2+1],f47[x*2]+f77[x*2+1]));
    f74[x]=max(f77[x*2]+f74[x*2+1],max(f74[x*2]+f44[x*2+1],f44[x*2]+f44[x*2+1]));
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    scanf("%s",s+1);
    build(1,1,n);
    for(;m;m--)
    {
        scanf("%s",s);
        if(s[0]=='c')
            printf("%d\n",max(f44[1],f47[1]));
        else
        {
            int l=read(),r=read();
            add(1,1,n,l,r);
        }
    }
}
145E
要算的是ΣC(n,r)*r^k
例如k=3,我们要求ΣC(n,r)*r^k
注意到长得很像(1+x)^n,等于ΣC(n,r)*x^r
现在对左右他求一次导再同时乘x
nx(1+x)^(n-1)=ΣC(n,r)*r*x^r
再求一次导数
nx(1+x)^(n-1)+n(n-1)x^2(1+x)^(n-2)=ΣC(n,r)*r^2*x^r
再求一次导数
nx(1+x)^(n-1)+3*n*(n-1)*x^2*(1+x)^(n-2)+n*(n-1)*(n-2)x^3(1+x)^(n-3)=ΣC(n,r)*r^3*x^r
令x=1,令f[a][b]表示第a次求导时,x^b(1+x)^(n-b)的系数
那么求完三次导数,等式左边为
f[3][1]*x*(1+x)^(n-1)+f[3][2]*x^2*(1+x)^(n-2)+f[3][3]*x^3*(1+x)^(n-3)
等式右边是ΣC(n,r)*r^3*x^r,可以发现如果令x=1,即为所求
所以我们计算出Σf[k][i]*2^(n-i)即为所求
高等数学知识告诉我们,f[i][j]=f[i-1][j-1]*(n-j+1)+f[i-1][j]*j
int f[5010][5010];
int n,k,ans,mod=1e9+7;
int quick(ll x,int y)
{
    ll t=1;
    while(y)
    {
        if(y&1)
            t=t*x%mod;
        x=x*x%mod;
        y=y/2;
    }
    return t;
}
int main()
{
    n=read();k=read();
    f[1][1]=n;
    for(int i=2;i<=k;i++)
        for(int j=1;j<=i;j++)
            f[i][j]=(1ll*f[i-1][j-1]*(n-j+1)+1ll*f[i-1][j]*j)%mod;
    int now=quick(2,n-1);
    int t=quick(2,mod-2);
    for(int i=1;i<=k;i++)
    {
        ans=(ans+1ll*f[k][i]*now)%mod;
        now=1ll*now*t%mod;
    }
    cout<<ans;
}
932E 基于k次求导
https://www.cnblogs.com/chdy/p/17476439.html
用第二类斯特林数化简x^k
关键在于把ΣC(n,x)C(x,i)i!变成Σ2^(n-i)*n!/(n-i)!
int s[5010][5010];
int n,k,ans,mod=1e9+7;
int quick(ll x,int y)
{
    ll t=1;
    while(y)
    {
        if(y&1)
            t=t*x%mod;
        x=x*x%mod;
        y=y/2;
    }
    return t;
}
int main()
{
    n=read();k=read();
    for(int i=1;i<=k;i++)
        for(int j=1;j<=i;j++)
            if(j==1)
                s[i][j]=1;
            else
                s[i][j]=(s[i-1][j-1]+1ll*j*s[i-1][j])%mod;
    int now=1ll*quick(2,n-1)*n%mod;
    int t=quick(2,mod-2);
    for(int i=1;i<=k;i++)
    {
        ans=(ans+1ll*s[k][i]*now)%mod;
        now=1ll*now*(n-i)%mod*t%mod;
    }
    cout<<ans;
}
932E基于第二类斯特林数
考虑dp
要记录的状态有到第几位了 i,有几个未关闭集合 j,已关闭的集合代价+未关闭的集合左端点到ai的距离和kk
那么答案是Σf[n][0][0-k]
对于i到i+1的转移
如果a[i+1]做左端点,那么j++,kk+=j*(a[i+1]-a[i]),方案数+=f[i][j][kk]
如果a[i+1]做右端点,那么j--,kk仍然+=那个,方案数+=f[i][j][kk]*j
若果a[i+1]做中间节点,那么j不变,kk仍然+=那个,方案数仍然+=f[i][j][kk]*j
如果a[i+1]自成一个集合,那么j不变,kk仍然+=那个,方案数+=f[i][j][kk]
如果ll f[210][210][1010]会越界,改成int或者滚动数组都可
int n,k;
int a[210],mod=1e9+7;
ll f[2][210][1010],ans;
int main()
{
    // freopen("1.in","r",stdin);
    n=read();k=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    sort(a+1,a+1+n);
    f[0][0][0]=1;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<=n;j++)
            for(int kk=0;kk<=k;kk++)
                f[i&1^1][j][kk]=0;
        for(int j=0;j<=n;j++)
            for(int kk=0;kk+j*(a[i+1]-a[i])<=k;kk++)
            {
                int t=kk+j*(a[i+1]-a[i]);
                f[i&1^1][j][t]=(f[i&1^1][j][t]+(1+j)*f[i&1][j][kk])%mod;
                f[i&1^1][j+1][t]=(f[i&1^1][j+1][t]+f[i&1][j][kk])%mod;
                if(j-1>=0)
                    f[i&1^1][j-1][t]=(f[i&1^1][j-1][t]+f[i&1][j][kk]*j)%mod;
            }
    }
    for(int i=0;i<=k;i++)
        ans=(ans+f[n&1][0][i])%mod;
    cout<<ans;
}
626F
考虑暴力做法
从左往右,要把ai变bi
寻找右边第一次出现bi的位置r,对区间[i,r]排序,如果区间最小值等于bi就继续向右移动,否则输出no
具有一般性,对于ai是否等于bi都可以这样做,复杂度n方logn
考虑用queue和线段树加速
找第一次出现bi的位置r使用queue
线段树维护ai数组的区间最小值
每次用掉一个位置的就删掉
这样区间询问[i,r]就是询问所有未被删的ai的,r之前的最小值
判断[i,r]区间最小值是否为bi即可
queue<int>pos[300010];
int n,a[300010],b[300010],c[300010],d[300010];
int minn[1200010];
void build(int x,int l,int r)
{
    if(l==r)
    {
        minn[x]=a[l];
        return ;
    }
    int mid=(l+r)/2;
    build(x*2,l,mid);
    build(x*2+1,mid+1,r);
    minn[x]=min(minn[x*2],minn[x*2+1]);
}
void add(int x,int l,int r,int d)
{
    if(l==r)
    {
        minn[x]=400000;
        return ;
    }
    int mid=(l+r)/2;
    if(d<=mid)
        add(x*2,l,mid,d);
    else
        add(x*2+1,mid+1,r,d);
    minn[x]=min(minn[x*2],minn[x*2+1]);
}
int ask(int x,int l,int r,int tr)
{
    if(r<=tr||minn[x]==400000)
        return minn[x];
    int mid=(l+r)/2;
    if(tr<=mid)
        return ask(x*2,l,mid,tr);
    else
        return min(minn[x*2],ask(x*2+1,mid+1,r,tr));
}
void work()
{
    n=read();
    for(int i=1;i<=n;i++)
        while(pos[i].size())
            pos[i].pop();
    for(int i=1;i<=n;i++)
        c[i]=a[i]=read();
    for(int i=1;i<=n;i++)
        d[i]=b[i]=read();
    sort(c+1,c+1+n);
    sort(d+1,d+1+n);
    for(int i=1;i<=n;i++)
        if(c[i]!=d[i])
        {
            printf("NO\n");
            return;
        }
    for(int i=1;i<=n;i++)
        pos[a[i]].push(i);
    build(1,1,n);
    for(int i=1;i<=n;i++)
    {
        int r=pos[b[i]].front();
        pos[b[i]].pop();
        if(ask(1,1,n,r)==b[i])
            add(1,1,n,r);
        else
        {
            printf("NO\n");
            return ;
        }
    }
    printf("YES\n");
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1187D
考虑跑一个生成树,最多会留下来21个边,21个边涉及的点最多有42个
对着42个点跑floyd,再预处理一下42个点到每个点的距离
那么x到y要么是直接走生成树的边
要么先到42个点之一的i,再走到42个点之一的j,再走到y
复杂度42nlogn+q*42*42
(话说距离相关的数组用了好多)
const int N=100010;
int n,m;
int fa[N][21],deep[N],a[50];
ll d[N],dis[50][50],dd[50][N];
int tot;
vector<pair<int,int> >ee[N];
struct edge
{
    int x,y;
    ll v;
}e[30];
void dfs(int x)
{
    for(auto y:ee[x])
    {
        if(y.first==fa[x][0])continue;
        fa[y.first][0]=x;
        deep[y.first]=deep[x]+1;
        d[y.first]=d[x]+y.second;
        dfs(y.first);
    }
}
int get(int x)
{
    return fa[x][0]==x?x:fa[x][0]=get(fa[x][0]);
}
ll lca(int x,int y)
{
    ll t=d[x]+d[y];
    if(deep[x]<deep[y])
        swap(x,y);
    for(int i=20;i>=0;i--)
        if(deep[fa[x][i]]>=deep[y])
            x=fa[x][i];
    if(x==y)
        return t-2*d[x];
    for(int i=20;i>=0;i--)
        if(fa[x][i]!=fa[y][i])
            x=fa[x][i],y=fa[y][i];
    return t-2*d[fa[x][0]];
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
        fa[i][0]=i;
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        ll v=read();
        if(get(x)==get(y))
        {
            tot++;
            e[tot]={x,y,v};
        }
        else
        {
            fa[get(x)][0]=get(y);
            ee[x].push_back({y,v});
            ee[y].push_back({x,v});
        }
    }
    fa[1][0]=0;
    deep[1]=1;
    dfs(1);
    for(int i=1;i<=20;i++)
        for(int x=1;x<=n;x++)
            fa[x][i]=fa[fa[x][i-1]][i-1];
    memset(dis,0x3f,sizeof(dis));
    for(int i=1;i<=tot;i++)
    {
        a[i]=e[i].x;
        a[i+tot]=e[i].y;
        dis[i][i+tot]=dis[i+tot][i]=min(dis[i][i+tot],e[i].v);
    }
    tot=tot*2;
    for(int i=1;i<=tot;i++)
        for(int j=i;j<=tot;j++)
            dis[i][j]=dis[j][i]=min(dis[i][j],lca(a[i],a[j]));
    for(int i=1;i<=tot;i++)
        for(int j=1;j<=tot;j++)
            for(int k=1;k<=tot;k++)
                dis[j][k]=min(dis[j][k],dis[j][i]+dis[i][k]);
    for(int i=1;i<=tot;i++)
        for(int x=1;x<=n;x++)
            dd[i][x]=lca(a[i],x);
    for(int q=read();q;q--)
    {
        int x=read(),y=read();
        ll ans=lca(x,y);
        for(int i=1;i<=tot;i++)
            for(int j=1;j<=tot;j++)
                ans=min(ans,dd[i][x]+dis[i][j]+dd[j][y]);
        printf("%lld\n",ans);
    }
}
1051F
比如样例
3 2
1 2 3
那么把1 2 3 写成一个多项式
x+x^2+x^3
再求它的k次方
(x+x^2+x^3)^2=x^2+2*x^3+3*x^4+2*x^5+x^6
有系数的项的次数即为所求:2,3,4,5,6
用多项式乘法快速幂即可。因为次数越界了会造成一些错误(我不太懂啊),我加上了每次乘之后把系数降为1就过了。
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
typedef double db;
typedef long long ll;
struct cp
{
    db x,y;
    cp(db real=0,db imag=0):x(real),y(imag){};
    cp operator +(cp b)const{return {x+b.x,y+b.y};}
    cp operator -(cp b)const{return {x-b.x,y-b.y};}
    cp operator*(cp b)const{return {x*b.x-y*b.y,x*b.y+y*b.x};}
};
using vcp=vector<cp>;
using Poly=vector<int>;
class Cipolla
{
    int P,I2{};
    using pll=pair<ll,ll>;
};
#define MUL(a,b) (ll(a)*(b)%P)
#define ADD(a,b) (((a)+=(b))>=P?(a)-=P:0)
#define SUB(a,b) (((a)-=(b))<0?9a)+=P:0)
const int P=1e9+7,N=2e5+10;
namespace FFT{
    const db pi=acos(-1);
    
    vcp Omega(int L){
        vcp w(L);w[1]=1;
        for(int i=2;i<L;i<<=1){
            auto w0=w.begin()+i/2,w1=w.begin()+i;
            cp wn(cos(pi/i),sin(pi/i));
            for(int j=0;j<i;j+=2)
            w1[j]=w0[j>>1],w1[j+1]=w1[j]*wn;
        }
        return w ;
    }
    auto W=Omega(1<<21);
    void DIF(cp *a,int n){
        cp x,y;
        for(int k=n>>1;k;k>>=1)
        for(int i=0;i<n;i+=k<<1)
        for(int j=0;j<k;j++)
            x=a[i+j],y=a[i+j+k],a[i+j+k]=(a[i+j]-y)*W[k+j],a[i+j]=x+y;
    }
    void IDIT(cp *a,int n){
        cp x,y;
        for(int k=1;k<n;k<<=1)
        for(int i=0;i<n;i+=k<<1)
            for(int j=0;j<k;j++)
                x=a[i+j],y=a[i+j+k]*W[k+j],a[i+j+k]=x-y,a[i+j]=x+y;
        const db Inv=1. /n;
        rep(i,0,n-1)a[i].x*=Inv,a[i].y*=Inv;
        reverse(a+1,a+n);
    }
}
namespace Polynomial{
    void DFT(vcp &a){FFT::DIF(a.data(),a.size());}
    void IDFT(vcp &a){FFT::IDIT(a.data(),a.size());}
    int norm(int n){return 1<<(__lg(n-1)+1);}
    void norm(Poly &a){if(!a.empty())a.resize(norm(a.size()),0);
    else
    a={0};}
    vcp &dot(vcp &a,vcp &b){rep(i,0,a.size()-1)a[i]=a[i]*b[i];
    return a;}
    
    Poly operator *(ll k,Poly a){
        Poly ans;
        for(auto i:a)
            ans.push_back(k*i);
            return ans;
    }
    Poly operator *(Poly a,Poly b){
        int n=a.size()+b.size()-1;
        vcp c(norm(n));
        rep(i,0,a.size()-1)c[i].x=a[i];
        rep(i,0,b.size()-1)c[i].y=b[i];
        DFT(c),dot(c,c),IDFT(c),a.resize(n);
        rep(i,0,n-1)a[i]=(int)(c[i].y*.5+.5);
        return a;
    }
}
using namespace Polynomial;
int a[1010];
int main()
{
    // freopen("1.in","r",stdin);
    // freopen("1.out","w",stdout);
    int n=read(),k=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    sort(a+1,a+1+n);
    Poly vec(a[n]+1),t;
    for(int i=1;i<=n;i++)
        vec[a[i]]=1;
    Poly ans=vec;
    k--;
    while(k)
    {
        if(k&1)
        {
            ans=ans*vec;
            for(int i=0;i<ans.size();i++)
                if(ans[i])
                    ans[i]=1;
        }
        vec=vec*vec;
        k=k/2;
        for(int i=0;i<vec.size();i++)
            if(vec[i])
                vec[i]=1;
    }
    for(int i=1;i<ans.size();i++)
        if(ans[i])
            printf("%d ",i);
}
632E
暴力的dp:f[i]=max(f[j]+max(a[j+1]...a[i])-min(a[j+1]...a[i])
考虑从i到1的max是单调增的,单调栈记录一下这些增大的节点,如果ai使前面的推栈了就更新区间值。min也相同地处理

int n,a[1000010],axx,inn;
ll f[1000010],c[4000010],lazy[4000010];
vector<pair<int,int>>maxx,minn;

void pushdown(int x)
{
    if(lazy[x])
    {
        lazy[x*2]+=lazy[x];
        lazy[x*2+1]+=lazy[x];
        c[x*2]+=lazy[x];
        c[x*2+1]+=lazy[x];
        lazy[x]=0;
    }
}
void add(int x,int l,int r,int tl,int tr,ll v)
{
    if(tl<=l&&r<=tr)
    {
        c[x]+=v;
        lazy[x]+=v;
        return ;
    }
    pushdown(x);
    int mid=(l+r)/2;
    if(tl<=mid)
        add(x*2,l,mid,tl,tr,v);
    if(tr>mid)
        add(x*2+1,mid+1,r,tl,tr,v);
    c[x]=max(c[x*2],c[x*2+1]);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();    
    // for(int i=1;i<=n;i++)
    // {
    //     a[i]=read();
    //     int minn=a[i],maxx=a[i];
    //     for(int j=i-1;j>=0;j--)
    //     {
    //         f[i]=max(f[i],f[j]+maxx-minn);
    //         minn=min(minn,a[j]);
    //         maxx=max(maxx,a[j]);
    //     }
    // }
    maxx.push_back({2e9,0});
    minn.push_back({-2e9,0});
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        while(maxx[axx].first<a[i])
        {
            add(1,0,n,maxx[axx-1].second,maxx[axx].second-1,a[i]-maxx[axx].first);
            maxx.pop_back();
            axx--;
        }
        while(minn[inn].first>a[i])
        {
            add(1,0,n,minn[inn-1].second,minn[inn].second-1,minn[inn].first-a[i]);
            minn.pop_back();
            inn--;
        }
        f[i]=c[1];
        add(1,0,n,i,i,f[i]);
        maxx.push_back({a[i],i});
        minn.push_back({a[i],i});
        axx++;
        inn++;
    }
    cout<<f[n];
}
484D
浅显的做法是nlog^3,用调和级数+二分+主席树,主席树维护区间里pre都落在了哪。显然是会超时的

int tot,lc[2000000],rc[2000000],c[2000000],pre[100010],rt[100010],n;
int build(int x,int l,int r,int d)
{
    tot++;
    int t=tot;
    lc[t]=lc[x];
    rc[t]=rc[x];
    c[t]=c[x]+1;
    if(l==r)
        return t;
    int mid=(l+r)/2;
    if(d<=mid)
        lc[t]=build(lc[x],l,mid,d);
    else
        rc[t]=build(rc[x],mid+1,r,d);
    return t;
}
int ask(int x1,int x2,int l,int r,int tl,int tr)
{
    if(x1==0&&x2==0||tl<=l&&r<=tr)
        return c[x2]-c[x1];
    int mid=(l+r)/2;
    if(tr<=mid)
        return ask(lc[x1],lc[x2],l,mid,tl,tr);
    else if(tl>mid)
        return ask(rc[x1],rc[x2],mid+1,r,tl,tr);
    else
        return ask(lc[x1],lc[x2],l,mid,tl,tr)+ask(rc[x1],rc[x2],mid+1,r,tl,tr);
}
int ask(int x,int k)
{
    int l=x,r=n,mid;
    while(l+1<r)
    {
        mid=(l+r)/2;
        if(ask(rt[x-1],rt[mid],0,n,0,x-1)>k)//区间x到mid有多少个pre落在0到x-1
            r=mid;
        else
            l=mid;
    }
    if(ask(rt[x-1],rt[r],0,n,0,x-1)<=k)
        return r;
    return l;
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        rt[i]=build(rt[i-1],0,n,pre[x]);
        pre[x]=i;
    }
    for(int k=1;k<=n;k++)
    {
        int ans=0;
        for(int i=1;i<=n;i=ask(i,k)+1)
            ans++;
        printf("%d ",ans);
    }
}


考虑把主席树维护的东西反过来,维护落在i上的pre都从哪来。那么就可以主席树上二分了。我只会求第一个等于k的pos,所以求第一个等于k+1的pos再减一就是最后一个等于k的pos了。复杂度nlog^2,调和级数+主席树上二分。
int tot,lc[2000000],rc[2000000],c[2000000],pre[100010],rt[100010],n;
queue<int>pos[100010];
int build(int x,int l,int r,int d)
{
    tot++;
    int t=tot;
    lc[t]=lc[x];
    rc[t]=rc[x];
    c[t]=c[x]+1;
    if(l==r)
        return t;
    int mid=(l+r)/2;
    if(d<=mid)
        lc[t]=build(lc[x],l,mid,d);
    else
        rc[t]=build(rc[x],mid+1,r,d);
    return t;
}
int ask(int x1,int x2,int l,int r,int tl,int tr)
{
    if(x1==0&&x2==0||tl<=l&&r<=tr)
        return c[x2]-c[x1];
    int mid=(l+r)/2;
    if(tr<=mid)
        return ask(lc[x1],lc[x2],l,mid,tl,tr);
    else if(tl>mid)
        return ask(rc[x1],rc[x2],mid+1,r,tl,tr);
    else
        return ask(lc[x1],lc[x2],l,mid,tl,tr)+ask(rc[x1],rc[x2],mid+1,r,tl,tr);
}
void build0(int x,int l,int r)
{
    if(l==r)
    {
        if(pos[0].size()&&l==pos[0].front())
            c[x]=1,pos[0].pop();
        return ;
    }
    int mid=(l+r)/2;
    tot++;
    lc[x]=tot;
    tot++;
    rc[x]=tot;
    build0(lc[x],l,mid);
    build0(rc[x],mid+1,r);
    c[x]=c[lc[x]]+c[rc[x]];
}
int askpos(int x,int l,int r,int k)
{
    if(l==r)
        return l;
    int mid=(l+r)/2;
    if(c[lc[x]]>=k)
        return askpos(lc[x],l,mid,k);
    else
        return askpos(rc[x],mid+1,r,k-c[lc[x]]);
}
int askc(int x,int l,int r,int d)
{
    if(r<=d)
        return c[x];
    int mid=(l+r)/2;
    if(d<=mid)
        return askc(lc[x],l,mid,d);
    else
        return c[lc[x]]+askc(rc[x],mid+1,r,d);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        
        pos[pre[x]].push(i);
        pre[x]=i;
    }
    tot++;
    build0(1,1,n+1);
    rt[0]=1;
    for(int i=1;i<=n;i++)
    {
        if(pos[i].size()==0)
        {
            rt[i]=rt[i-1];
            continue;
        }
        rt[i]=build(rt[i-1],1,n+1,pos[i].front());
    }
    for(int k=1;k<=n;k++)
    {
        int ans=0;
        for(int i=1;i<=n;)
        {
            ans++;
            i=askpos(rt[i-1],1,n+1,k+askc(rt[i-1],1,n+1,i));
        }
        printf("%d ",ans);
    }
}
786C
挺正常的树形dp
考虑b比较大不能背包,可以维护f[x][i][0/1]表示在x的子树中买了i个商品,x是否用了优惠
从上往下dp,才不会重复计算
以及f数组开ll
时间复杂度是Σsiz[y]*Σsiz[比我先来的兄弟子树],是小于n^2的
int n,b;
int d[5010],c[5010],siz[5010];
ll f[5010][5010][2];
vector<int>e[5010];
void dfs(int x)
{
    f[x][0][0]=0;
    f[x][1][0]=d[x];
    f[x][1][1]=d[x]-c[x];
    siz[x]=1;
    for(auto y:e[x])
    {
        dfs(y);
        for(int i=siz[x];i>=0;i--)
            for(int j=siz[y];j>=1;j--)
            {
                f[x][i+j][0]=min(f[x][i+j][0],f[x][i][0]+f[y][j][0]);
                f[x][i+j][1]=min(f[x][i+j][1],f[x][i][1]+min(f[y][j][1],f[y][j][0]));
            }
        siz[x]+=siz[y];
    }
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();b=read();
    d[1]=read();c[1]=read();
    for(int i=2;i<=n;i++)
    {
        d[i]=read();c[i]=read();
        e[read()].push_back(i);
    }
    memset(f,0x3f,sizeof(f));
    dfs(1);
    for(int i=n;i>=0;i--)
        if(f[1][i][0]<=b||f[1][i][1]<=b)
        {
            cout<<i;
            return 0;
        }
}
815C
从大往小考虑
如果他需要i个人就会投我票,但是人数已经大于了i,那么就可以先放放
具体体现在继续留在堆里
priority_queue<int,vector<int>,greater<int>>q;
vector<int>o[200010];
int n;
ll ans;
void work()
{
    n=read();ans=0;
    while(q.size())q.pop();
    for(int i=0;i<=n;i++)
        o[i].clear();
    for(int i=1;i<=n;i++)
    {
        int m=read();
        o[m].push_back(read());
    }
    for(int i=n;i>=0;i--)
    {
        for(auto x:o[i])
            q.push(x);
        while(q.size()>n-i)
        {
            ans+=q.top();
            q.pop();
        }
    }
    printf("%lld\n",ans);
}
int main()
{
    for(int t=read();t;t--)
        work();
}
1251E1
考虑如果没有横贯整个区域的线段
那么每个线段之间的交点都会多产生一个区域
但是如果每多加一个l=0,r=1e6的线段,区域也会加一
所以答案为1+(l=0&&r=1e6)线段数量+线段交点数量,是经典问题,2023ccpc河南省赛I题就考过,可以用线段树或者主席树搞一搞。
int n,m;
vector<int>jia[1000010],jian[1000010];
ll ans;
int c[4000010];
struct node
{
    int x,l,r;
    friend bool operator <(node a,node b)
    {
        return a.x<b.x;
    }
}o[100010];
void add(int x,int l,int r,int d,int v)
{
    c[x]+=v;
    if(l==r)
        return ;
    int mid=(l+r)/2;
    if(d<=mid)
        add(x*2,l,mid,d,v);
    else
        add(x*2+1,mid+1,r,d,v);
}
int ask(int x,int l,int r,int tl,int tr)
{
    if(tl<=l&&r<=tr)
        return c[x];
    int mid=(l+r)/2;
    if(tr<=mid)
        return ask(x*2,l,mid,tl,tr);
    else if(tl>mid)
        return ask(x*2+1,mid+1,r,tl,tr);
    else
        return ask(x*2,l,mid,tl,tr)+ask(x*2+1,mid+1,r,tl,tr);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    ans=1;
    for(int i=1;i<=n;i++)
    {
        int y=read(),l=read(),r=read();
        jia[l].push_back(y);
        jian[r].push_back(y);
        if(l==0&&r==1000000)
            ans++;
    }
    for(int i=1;i<=m;i++)
    {
        o[i].x=read();
        o[i].l=read();
        o[i].r=read();
        if(o[i].l==0&&o[i].r==1000000)
            ans++;
    }
    sort(o+1,o+1+m);
    for(auto x:jia[0])
        add(1,1,1000000,x,1);
    for(int i=1;i<=m;i++)
    {
        for(int j=o[i-1].x+1;j<=o[i].x;j++)
            for(auto x:jia[j])
                add(1,1,1000000,x,1);
        for(int j=o[i-1].x;j<o[i].x;j++)
            for(auto x:jian[j])
                add(1,1,1000000,x,-1);
        ans+=ask(1,1,1000000,o[i].l,o[i].r);
    }
    cout<<ans;
}
1401E
打得我道心破碎
考虑左端点从右往左移动,每个右端点的贡献不断增大,且随着右端点增大而增大。
所以用线段树维护区间里最小最大值,那么区间是同一个值当且仅当minn==maxx,区间恰好是单调上升的区间当且仅当maxx-minn==r-l
只对这种区间修改,否则暴力递归左右子区间即可。
int n;
ll now,ans;
char s[500010];
int maxx[2000010],minn[2000010];
int lmax[2000010],lmin[2000010];
void pushdown(int x,int l,int r)
{
    if(lmin[x]==lmax[x])
        lmin[x*2]=minn[x*2]=lmax[x*2]=maxx[x*2]=lmin[x*2+1]=minn[x*2+1]=lmax[x*2+1]=maxx[x*2+1]=lmin[x];
    else
        lmin[x*2]=minn[x*2]=lmin[x],lmin[x*2+1]=minn[x*2+1]=lmin[x]+r-l,
        lmax[x*2]=maxx[x*2]=lmin[x*2+1]-1,lmax[x*2+1]=maxx[x*2+1]=lmax[x];
    lmax[x]=lmin[x]=0;
}
void add1(int x,int l,int r,int tl,int tr,int v)
{
    if(tl<=l&&minn[x]>=v-tl+l)
        return ;
    if(tl<=l&&r<=tr)
    {
        if(minn[x]==maxx[x]||r-l==maxx[x]-minn[x])//区间为一个值
        {
            v=v-tl+l;
            now=now+1ll*(v+v+r-l-maxx[x]-minn[x])*(r-l+1)/2;
            minn[x]=lmin[x]=v;//懒标记
            maxx[x]=lmax[x]=v+r-l;
            return ;
        }
    }
    int mid=(l+r)/2;
    if(lmax[x]!=0)
        pushdown(x,l,mid+1);
    if(tl<=mid)
        add1(x*2,l,mid,tl,tr,v);
    if(tr>mid)
        add1(x*2+1,mid+1,r,tl,tr,v);
    minn[x]=minn[x*2];
    maxx[x]=maxx[x*2+1];
}
void add2(int x,int l,int r,int tl,int v)
{
    if(minn[x]>=v)
        return ;
    if(tl<=l)
    {
        if(minn[x]==maxx[x]||r-l==maxx[x]-minn[x]&&maxx[x]<=v)//区间为一个值
        {
            now=now+1ll*v*(r-l+1)-1ll*(maxx[x]+minn[x])*(r-l+1)/2;
            lmax[x]=lmin[x]=maxx[x]=minn[x]=v;//懒标记
            return ;
        }
    }
    int mid=(l+r)/2;
    if(lmax[x]!=0)
        pushdown(x,l,mid+1);
    if(tl<=mid)
        add2(x*2,l,mid,tl,v);
    add2(x*2+1,mid+1,r,tl,v);
    minn[x]=minn[x*2];
    maxx[x]=maxx[x*2+1];
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    scanf("%s",s+1);
    for(int i=n,len=0;i>=1;i--)
    {
        if(s[i]=='0')
        {
            ans+=now;
            len=0;
            continue;
        }
        len++;
        if(i+len<=n)
            add2(1,1,n,i+len,len);
        add1(1,1,n,i,i+len-1,1);
        ans+=now;
    }
    cout<<ans;
}
1428F
注意到任意k满足条件,则2k,4k,8k也满足条件(这些k的倍数小于等于n时
所以只考虑n/2+1这些长度即可
假如长为7
则需考虑4567长度
4长度时,区间和设为s,s+x-a1,s+2x-a1-a2,s+3x-a1-a2-a3
5长度时,区间和为s+x,s+2x-a1,s+3x-a1-a2
6长度时,区间和为s+2x,s+3x-a1
7长度时,区间和为s+3x
要做的是枚举长度,看看最小区间和是否大于0
注意到每次k增大时,最后一个区间和会被踢掉,其他区间同时加x
所以考虑把区间长度是n/2+1时的区间和带着左端点的信息,扔到小根堆里

对于长度增加要踢掉的区间和,不在考虑范围内,可以一个while循环踢掉
然后判断这个最小值+偏移量是否大于0即可
int read()
{
    int x;scanf("%d",&x);return x;
}
priority_queue<pair<ll,int>,vector<pair<ll,int>>,greater<pair<ll,int>>>q;
ll sum,x;
int n,t,a[500010];
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    t=n/2+1;
    for(int i=1;i<=t;i++)
        sum+=(a[i]=read());
    x=read();
    q.push({sum,1});
    for(int i=t+1;i<=n;i++)
    {
        sum=sum+x-a[i-t];
        q.push({sum,i-t+1});
    }
    for(int k=t,i=n-t+1;k<=n;k++,i--)
    {
        while(q.top().second>i)
            q.pop();
        if(q.top().first+(k-t)*x>0)
        {
            cout<<k;
            return 0;
        }
    }
    cout<<-1;
}
1358E
不会,抄的https://blog.csdn.net/LBCLZMB/article/details/107503613

int n,m,w[200010],d[200010],v[200010],ok[200010];
vector<pair<int,int>>e[200010];
queue<int>q;
stack<int>ans;
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
        w[i]=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        d[x]++;
        d[y]++;
        e[x].push_back({y,i});
        e[y].push_back({x,i});
    }
    for(int i=1;i<=n;i++)
        if(w[i]>=d[i])
        {
            q.push(i);
            v[i]=1;
        }
    while(q.size())
    {
        int x=q.front();q.pop();
        for(auto y:e[x])
        {
            if(!ok[y.second])
            {
                ans.push(y.second);
                ok[y.second]=1;
            }
        }
        for(auto y:e[x])
        {
            d[y.first]--;
            if(w[y.first]>=d[y.first])
            {
                if(!v[y.first])
                    q.push(y.first),v[y.first]=1;
            }
        }
    }
    if(ans.size()==m)
    {
        printf("ALIVE\n");
        while(ans.size())
            printf("%d ",ans.top()),ans.pop();
    }
    else
        printf("DEAD");
}
1369E
考虑记忆化区间dp
f[l][r][res]表示刚到这个区间,带着res的和l相同的来的
那么如果l==r,不得不计算代价为a[res+len[l]]
否则要么拿分,再往后走,a[res+len[l]]+ask(l+1,r,0)
要么把res+len[l]拖到后面,ask(l+1,j-1,0)+ask(j,r,res+len[l])
取max即可
int n,tot,len[110];
char s[110];
int v[110][110][110];
ll f[110][110][110],a[110];
ll ask(int l,int r,int res)
{
    if(v[l][r][res])
        return f[l][r][res];
    v[l][r][res]=1;
    if(l==r)
        return f[l][r][res]=a[res+len[l]];
    f[l][r][res]=a[res+len[l]]+ask(l+1,r,0);//先拿下再往后走
    for(int j=l+2;j<=r;j+=2)
        f[l][r][res]=max(f[l][r][res],ask(l+1,j-1,0)+ask(j,r,res+len[l]));//先往后走再一块吃
    return f[l][r][res];
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    scanf("%s",s+1);
    for(int i=1;i<=n;i++)
    {
        tot++;
        len[tot]=1;
        while(s[i+1]==s[i])
        {
            len[tot]++;
            i++;
        }
    }
    for(int i=1;i<=n;i++)
    {
        a[i]=max(a[i],read());
        for(int j=i;j<=n;j++)
            a[j]=max(a[j],a[j-i]+a[i]);
    }
    cout<<ask(1,tot,0);
}
1107E
会有(n+1)/2认同答案,所以大胆随机取20个人,计算他的子集。答案不在这20个人的子集里的概率非常小。
那么问题转变成对于一个人的2^15的子集,计算每个子集出现的次数,判断是否大于等于(n+1)/2。我感觉很眼熟,想了半天想起来就是这篇博客上面,449D的同款问题。
vector<int>pos[200010];
int n,m,p,f[200010];
ll val[200010],maxx,ans;
char s[100];
int ask(int x)
{
    int sum=0;
    while(x)
    {
        sum=sum+(x&1);
        x=x/2;
    }
    return sum;
}
void work(int x)
{
    int k=pos[x].size();
    for(int i=0;i<(1<<(k+1));i++)
        f[i]=0;
    for(int i=1;i<=n;i++)
    {
        int t=0;
        for(int j=0;j<k;j++)
            if(val[i]&(1ll<<pos[x][j]))
                t=t*2+1;
            else
                t=t*2;
        f[t]++;
    }
    for(int j=0;j<=k;j++)
        for(int i=0;i<(1<<k);i++)
            if((i&(1<<j))==0)
                f[i]=f[i]+f[i+(1<<j)];

    for(int i=0;i<(1<<k);i++)
        if(f[i]>=(n+1)/2&&ask(i)>maxx)
        {
            maxx=ask(i);
            ans=0;
            for(int j=k-1;j>=0;j--)
                if(i&(1<<j))
                    ans=ans+(1ll<<pos[x][k-j-1]);
        }
}
int main()
{
    // freopen("1.in","r",stdin);
    srand(time(0));
    n=read();
    m=read();
    p=read();
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s+1);
        for(int j=1;j<=m;j++)
        {
            val[i]=val[i]*2+s[j]-'0';
            if(s[j]=='1')
                pos[i].push_back(m-j);
        }
    }
    for(int t=1;t<=20;t++)
        work(1ll*rand()*rand()%n+1);
    for(int i=m-1;i>=0;i--)
        cout<<bool(ans&(1ll<<i));
}
1523D
一点不会,抄的题解
int n,m,a[10010],vis[10010][1010],d[10010][1010];
deque<pair<int,int>>q;
ll g,r;
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;i++)
        a[i]=read();
    a[m+1]=n;
    sort(a+0,a+1+m+1);
    g=read();r=read();
    q.push_back({0,0});
    vis[0][0]=1;
    ll ans=-1;
    while(q.size())
    {
        int x=q.front().first;
        int t=q.front().second;
        q.pop_front();
        if(t==0)
        {
            int to=n-a[x];
            if(to<=g)
            {
                if(ans==-1||ans>(r+g)*d[x][t]+to)
                    ans=(r+g)*d[x][t]+to;
            }
        }
        if(t==g)
        {
            if(vis[x][0]==0)
            {
                d[x][0]=d[x][t]+1;
                q.push_back({x,0});
                vis[x][0]=1;
            }
            continue;
        }
        if(x)
        {
            int to=t+a[x]-a[x-1];
            if(to<=g&&vis[x-1][to]==0)
            {
                vis[x-1][to]=1;
                d[x-1][to]=d[x][t];
                q.push_front({x-1,to});
            }
        }
        if(x<m-1)
        {
            int to=t+a[x+1]-a[x];
            if(to<=g&&vis[x+1][to]==0)
            {
                vis[x+1][to]=1;
                d[x+1][to]=d[x][t];
                q.push_front({x+1,to});
            }
        }
    }
    cout<<ans;
}
1340C
考虑每次加点x和y,直径改变最多只会改变一个端点到x或者y。
所以先把树建一下,准备lca。然后扫一下修改操作,大胆更新。因为加点xy时只会有一个变成直径的端点,所以只用x判断一下即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int read()
{
    int x;scanf("%d",&x);return x;
}
vector<int>e[1000010];
int fa[1000010][25],n,d[1000010];
void dfs(int x)
{
    for(auto y:e[x])
    {
        fa[y][0]=x;
        d[y]=d[x]+1;
        dfs(y);
    }
}
int lca(int x,int y)
{
    int t=d[x]+d[y];
    if(d[x]>d[y])
        swap(x,y);
    for(int i=21;i>=0;i--)
        if(d[fa[y][i]]>=d[x])
            y=fa[y][i];
    if(x==y)
        return t-2*d[y];
    for(int i=21;i>=0;i--)
        if(fa[x][i]!=fa[y][i])
            x=fa[x][i],y=fa[y][i];
    return t-2*d[fa[x][0]];
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    e[1].push_back(2);
    e[1].push_back(3);
    e[1].push_back(4);
    for(int i=1,x=5,y=6;i<=n;x+=2,y+=2,i++)
    {
        int t=read();
        e[t].push_back(x);
        e[t].push_back(y);
    }
    dfs(1);
    for(int i=1;i<=21;i++)
        for(int x=1;x<=4+2*n;x++)
            fa[x][i]=fa[fa[x][i-1]][i-1];
    int ans=2,l=2,r=3;
    for(int i=1,x=5,y=6;i<=n;i++,x+=2,y+=2)
    {
        int dl=lca(x,l);
        int dr=lca(x,r);
        if(ans<=dr&&dl<=dr)
        {
            l=x;
            ans=dr;
        }
        else if(ans<=dl&&dr<=dl)
        {
            r=x;
            ans=dl;
        }
        printf("%d\n",ans);
    }
}
379F
看了好多个题解,都没看懂,逐渐怀疑人生。于是按我的想法写了写就过了。
首先写一个显然能优化的n^4的复杂度的dp:f[i][x][y]=1表示到了第i个果树之后,能剩下x个红的,y个绿的。那么最后枚举f[n][x][y],如果为1,则更新答案为max(ans,(sum-x-y)/k)。
那么dp的时候,枚举第i个果树,有l个红的和k-l个绿的组合了,这需要l<=a[i]并且0<=k-l<=b[i],那么还剩a[i]-l个红的,b[i]-k+l个绿的,f[i][x][y]可以转移到f[i][(x+a[i]-l)%k][(y+b[i]-(k-l)%k)%k]上面。
ll n,k,ans,sum;
bool f[510][510][510];
ll a[510],b[510];
int main()
{
    // freopen("1.in","r",stdin);
    n=read();k=read();
    f[0][0][0]=1;
    for(int i=1;i<=n;i++)
    {
        a[i]=read();b[i]=read();
        sum+=a[i]+b[i];
    }
    for(int i=1;i<=n;i++)
        for(int x=0;x<k;x++)
            for(int y=0;y<k;y++)
            {
                if(f[i-1][x][y]==0)
                    continue;
                for(int l=0;l<=min(k,a[i]);l++)
                    if((k-l)%k<=b[i])
                        f[i][(x+a[i]-l)%k][(y+b[i]-(k-l)%k)%k]=1;
            }
    for(int x=0;x<k;x++)
        for(int y=0;y<k;y++)
            if(f[n][x][y])
                ans=max(ans,(sum-x-y)/k);
    cout<<ans;
}
这个代码果然超时了,T33,于是心满意足地去优化。
考虑到x和y的关系:f[i][x][y]是合法的当且仅当y=(a1+a2+...ai-x)%k,于是显然可以省略掉一个循环。

ll n,k,ans,sum,tsum;
// bool f[510][510][510];
bool f[510][510];
ll a[510],b[510];
int main()
{
    // freopen("1.in","r",stdin);
    n=read();k=read();
    // f[0][0][0]=1;
    f[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        a[i]=read();b[i]=read();
        sum+=a[i]+b[i];
    }
    for(int i=1;i<=n;i++)
    {
        tsum=(tsum+a[i]+b[i])%k;
        for(int x=0;x<k;x++)
        {
            // int y=(tsum-x+k)%k;
            // for(int y=0;y<k;y++)
            {
                // if(f[i-1][x][y]==0)
                if(f[i-1][x]==0)
                    continue;
                for(int l=0;l<=min(k,a[i]);l++)
                    if((k-l)%k<=b[i])
                    {
                        // f[i][(x+a[i]-l)%k][(y+b[i]-(k-l)%k)%k]=1;
                        f[i][(x+a[i]-l)%k]=1;
                    }
            }
        }
    }
    for(int x=0;x<k;x++)
    {
        int y=(tsum-x+k)%k;
        // for(int y=0;y<k;y++)
        // if(f[n][x][y])
        if(f[n][x])
            ans=max(ans,(sum-x-y)/k);
    }
    cout<<ans;
}
1348E
感觉难在实现
考虑现在要把一个连通块判定是否是一个fib树
设为第k项,则需要把它分为一块k-1,一块k-2
注意到k-1还可以分出来k-2和k-3
所以遇到子树大小为k-1或者k-2直接切断即可
代码实现时,把边定义vector<pair<int,bool>>,为对xy切断时遍历x的边,遇到y让bool=1。遍历y的边,遇到x让bool等于1。之后递归地判断xy所在连通块是否是fib树。
vector<pair<int,bool>>G[200010];
int n,siz[200010];
vector<int>fib;
void NO()
{
    printf("NO");
    exit(0);
}
void getsiz(int u,int fa)
{
    siz[u]=1;
    for(auto e:G[u])
    {
        if(e.second)continue;
        int v=e.first;
        if(v==fa)continue;
        getsiz(v,u);
        siz[u]+=siz[v];
    }
}
void cutedge(int u,int fa,int k,int &pu,int &pv,int &kd)
{
    for(auto e:G[u])
    {
        if(pu)return ;
        if(e.second)continue;
        int v=e.first;
        if(v==fa)
            continue;
        if(siz[v]==fib[k-1]||siz[v]==fib[k-2])
        {
            pu=u,pv=v;
            kd=(siz[v]==fib[k-1])?k-1:k-2;
            break;
        }
        cutedge(v,u,k,pu,pv,kd);
    }
}
void check(int u,int k)
{
    if(k<=1)return ;
    getsiz(u,0);
    int pu=0,pv=0,kd=0;
    cutedge(u,0,k,pu,pv,kd);
    if(!pu)
        NO();
    for(auto &e:G[pu])
        if(e.first==pv)
            e.second=true;
    for(auto &e:G[pv])
        if(e.first==pu)
            e.second=true;
    check(pv,kd);
    check(pu,kd==k-1?k-2:k-1);
}
int main()
{
    cin>>n;
    fib.push_back(1);
    fib.push_back(1);
    for(int i=1;fib[i]<n;i++)
        fib.push_back(fib[i]+fib[i-1]);
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        G[u].push_back({v,0});
        G[v].push_back({u,0});
    }
    if(fib[fib.size()-1]!=n)
        NO();
    check(1,fib.size()-1);
    cout<<"YES";
}
1491E
首先写了一个暴力代码找规律,可以发现对n=5,sum相同时,稳定状态也相同。
而且是一个等差数列,但是可能存在一个位置相同。
int n,a[1000],b[1000];
int main()
{
    n=5;
    for(int kkk=5;kkk<=30;kkk++)
    {
        for(int i=1;i<n;i++)
            a[i]=i;
        a[n]=kkk;
        while(1)
        {
            int flag=0;
            for(int i=1;i<n;i++)
                if(a[i]+2<=a[i+1])
                    flag=1;
            if(!flag)
                break;
            for(int i=1;i<=n;i++)
            {
                if(i!=1&&a[i-1]+2<=a[i]&&a[i]+2<=a[i+1])
                    b[i]=a[i];
                else if(i!=1&&a[i-1]+2<=a[i])
                    b[i]=a[i]-1;
                else if(a[i]+2<=a[i+1])
                    b[i]=a[i]+1;
                else
                    b[i]=a[i];
            }
            for(int i=1;i<=n;i++)
                a[i]=b[i];
        }
        for(int i=1;i<=n;i++)
            cout<<a[i]<<' ';
        cout<<endl;
    }
}

考虑对每个位置二分答案,寻找最后一个满足x+(x+x+n-2)*(n-1)/2<=sum的值,即为ai
每次求出ai后让n--,sum-=ai

ll ask(ll n,ll sum)
{
    ll l=0,r=1e12,mid;
    while(l+1<r)
    {
        mid=(l+r)/2;
        if(mid+(mid+mid+n-2)*(n-1)/2>sum)
            r=mid;
        else
            l=mid;
    }
    if(r+(r+r+n-2)*(n-1)/2<=sum)
        return r;
    return l;
}
int n;
ll sum;
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
        sum+=read();
    for(int i=1;i<=n;i++)
    {
        ll ai=ask(n-i+1,sum);
        sum-=ai;
        cout<<ai<<' ';
    }

}
1392F
镇难啊
考虑把最大值放在第一个,这样只允许1走1-n,走到后面,而2、3、4都不能走1-n这条边。
接下来从后往前,维护一个单调栈,再用线段树维护单调栈里面有哪些值
那么对于i及i以后的单调栈,可以用于计算以i-1为左端点的区间数量
如果a[i-1]>=a[i],则ans+=min(单调栈里元素数量,单调栈里小于等于a[i-1]的数字数量+1),既一个允许向下一次,立刻向上的圆弧,并允许往上走一下
例如5 2 3 4 5 6,以第一个5为左端点,所有的其他数字都可以做右端点。
如果a[i-1]<a[i],则只允许向右走一下子,ans++
例如5 6 1 2 3 7,以5为左端点,只允许以6为右端点
除此之外,作为a[1]的最大值有可能走到后面
例如5 2 3 4 3 2,以1为左端点,正着看有52,5235234。但是走1-n的52,523就会没有记入答案。再从后往前跑一遍即可。
int n,m;
int b[1000010],a[1000010],c[4000010];//离散化数组与原数组与线段树数组
int vis[1000010];
vector<int>o[1000010];
stack<pair<int,int>>q;
ll ans;
void add(int x,int l,int r,int p,int v)
{
    c[x]+=v;
    if(l==r)
        return ;
    int mid=(l+r)/2;
    if(p<=mid)
        add(x*2,l,mid,p,v);
    else
        add(x*2+1,mid+1,r,p,v);
}
int ask(int x,int l,int r,int v)//小于等于v的数量
{
    if(v>=r)
        return c[x];
    int mid=(l+r)/2;
    if(v>mid)
        return c[x*2]+ask(x*2+1,mid+1,r,v);
    else
        return ask(x*2,l,mid,v);
}
int main()
{
    // freopen("1.in","r",stdin);
    // freopen("1.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)
        b[i]=read();
    int maxx=1;
    for(int i=1;i<=n;i++)
        if(b[i]>b[maxx])
            maxx=i;
    for(int i=maxx;i<=n;i++)
        a[i-maxx+1]=b[i];
    for(int i=1;i<maxx;i++)
        a[i+n-maxx+1]=b[i];
    sort(b+1,b+1+n);
    m=unique(b+1,b+1+n)-b-1;
    for(int i=1;i<=n;i++)
        a[i]=lower_bound(b+1,b+1+m,a[i])-b;
    for(int i=n;i>1;i--)
    {
        while(q.size()&&a[i]>q.top().first)
        {
            add(1,0,m,q.top().first,-1);
            vis[q.top().second]=0;
            q.pop();
        }
        q.push({a[i],i});
        vis[i]=1;
        add(1,0,m,q.top().first,1);
        if(a[i-1]>=a[i])
            ans=ans+min(c[1],ask(1,0,m,a[i-1])+1);
        else
            ans=ans+1;
    }
    maxx=0;
    for(int i=n;i>=1;i--)
    {
        if(vis[i])
            break;
        if(a[i]>=maxx)
        {
            ans++;
            maxx=a[i];
        }
    }
    cout<<ans;
}
5E
i=1的答案显然是n,此后答案是单调不增的。
考虑什么时候答案会变小
既有一个炸弹放在了n后面时
例如n *
那么n-1能不能用呢
要查看n,n-1和炸弹的相对位置了
例如* n n-1 * 就可以用n-1
*****n * n-1 也可以
**n n-1 **就不可以
思考一下发现可以使用线段树,判断能不能用i时,把炸弹位置加一,大于i的位置减一,则需要所有包含pos[i]的区间都要区间和<=0。所以考虑维护以r为右端点的最小值,以l为左端点的最大值。需要判断的是以pos[i]为右端点的最小值和以pos[i]+1为左端点的最大值相加小于等于0
如果满足,i就可以当答案。否则需要继续减小。
int pos[300010];
int n,maxx[1200010],sum[1200010],minn[1200010];
void add(int x,int l,int r,int d,int v)
{
    if(l==r)
    {
        sum[x]+=v;
        minn[x]+=v;
        maxx[x]+=v;
        return ;
    }
    int mid=(l+r)/2;
    if(d<=mid)
        add(x*2,l,mid,d,v);
    else
        add(x*2+1,mid+1,r,d,v);
    sum[x]=sum[x*2]+sum[x*2+1];
    maxx[x]=max(maxx[x*2],sum[x*2]+maxx[x*2+1]);
    minn[x]=min(minn[x*2+1],sum[x*2+1]+minn[x*2]);
}

pair<int,int> askminn(int x,int l,int r,int tl,int tr)
{
    if(tl<=l&&r<=tr)
        return {sum[x],minn[x]};
    int mid=(l+r)/2;

    if(tr<=mid)
        return askminn(x*2,l,mid,tl,tr);
    if(tl>mid)
        return askminn(x*2+1,mid+1,r,tl,tr);
    pair<int,int>t1=askminn(x*2,l,mid,tl,tr);
    pair<int,int>t2=askminn(x*2+1,mid+1,r,tl,tr);

    return {t1.first+t2.first,min(t2.second,t2.first+t1.second)};
}
pair<int,int> askmaxx(int x,int l,int r,int tl,int tr)
{
    if(tl<=l&&r<=tr)
        return {sum[x],maxx[x]};
    int mid=(l+r)/2;

    if(tr<=mid)
        return askmaxx(x*2,l,mid,tl,tr);

    if(tl>mid)
        return askmaxx(x*2+1,mid+1,r,tl,tr);
    pair<int,int>t1=askmaxx(x*2,l,mid,tl,tr);
    pair<int,int>t2=askmaxx(x*2+1,mid+1,r,tl,tr);
    return {t1.first+t2.first,max(t1.second,t1.first+t2.second)};
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
        pos[read()]=i;
    int ans=n;
    for(int i=1;i<=n;i++)
    {
        while(askmaxx(1,1,n+1,pos[ans]+1,n+1).second+askminn(1,1,n+1,1,pos[ans]).second>0)
        {
            add(1,1,n+1,pos[ans],-1);
            ans--;
        }
        printf("%d ",ans);
        int x=read();
        add(1,1,n+1,x,1);
    }
}
1326E
一个方法是n^2地枚举所有段,计算mex。最后扫一下大伙,谁第一个没出现
然后我想到了从小到大枚举,看看x是否在mex里没有出现过。我们可以把所有的x看做隔档,看隔开后的小段是否凑齐了1到x-1
如果存在隔开后的小段mex为x,则该考虑x+1了。否则答案就是x了
可以发现我们把O(n^2)个子段变成了O(n)个子段。
求区间mex可以离线地使用莫队或者线段树。恰好可以发现得到O(n)个子段后,又变得可以离线了。
综上。我们用每个相邻的x做隔断,得到O(n)个子段。他们的mex即为全体子段的mex可能值。
然后离线地使用莫队求一下mex即可。
struct node
{
    int l,r,v,b;//左端点,右端点,被谁分隔,所属于的块
    friend bool operator<(node a,node b)
    {
        return a.b==b.b?a.r<b.r:a.b<b.b;
    }
}o[300010];
int m,sum[300010],now=1,ans=3,n,a[300010],flag[300010];
vector<int>pos[300010];
void qu(int x)
{
    sum[a[x]]--;
    if(sum[a[x]]==0)
        now=min(now,a[x]);
}
void ad(int x)
{
    sum[a[x]]++;
    while(sum[now])
        now++;
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        pos[a[i]].push_back(i);
    }
    if(pos[1].size()==n)//只有1
    {
        printf("1");
        return 0;
    }
    else if(pos[1].size()==0)//没有1
    {
        printf("2");
        return 0;
    }
    int s=sqrt(n);
    for(int i=3;i<=n+1;i++)
    {
        if(pos[i].size()==0)
        {
            o[++m]={1,n,i,1};
            break;
        }
        else
        {
            if(pos[i][0]!=1)
                o[++m]={1,pos[i][0]-1,i,1};
            if(pos[i][pos[i].size()-1]!=n)
                o[++m]={pos[i][pos[i].size()-1]+1,n,i,pos[i][pos[i].size()-1]/s+1};
            for(int j=0;j+1<pos[i].size();j++)
            {
                if(pos[i][j]+1!=pos[i][j+1])
                    o[++m]={pos[i][j]+1,pos[i][j+1]-1,i,pos[i][j]/s+1};
            }
        }
    }
    int l=1,r=0;
    sort(o+1,o+1+m);
    for(int i=1;i<=m;i++)
    {
        while(r<o[i].r)
        {
            r++;
            ad(r);
        }
        while(r>o[i].r)
        {
            qu(r);
            r--;
        }
        while(l<o[i].l)
        {
            qu(l);
            l++;
        }
        while(l>o[i].l)
        {
            l--;
            ad(l);
        }
        if(now==o[i].v)
            flag[now]=1;
    }
    while(flag[ans])
        ans++;
    cout<<ans;
}
1436E
这题挺套路的
三维偏序
考虑第一维可以排个序,然后双指针一下
第二维可以用离散化一下,作为线段树的"下标"
第三位用线段树维护
总的来说,维护区间最大值。则对于i号人,只需要看大于i的i的那些人,有没有r大于i的r的即可。
int n,a[500010],m;
int maxx[2000010],ans;
struct node
{
    int b,i,r;
    friend bool operator <(node a,node b)
    {
        return a.b>b.b;//从大到小地排序
    }
}o[500010];
void add(int x,int l,int r,int d,int v)
{
    maxx[x]=max(maxx[x],v);
    if(l==r)
        return ;
    int mid=(l+r)/2;
    if(d<=mid)
        add(x*2,l,mid,d,v);
    else
        add(x*2+1,mid+1,r,d,v);
}
int ask(int x,int l,int r,int tl,int tr)
{
    if(tl<=l&&r<=tr)
        return maxx[x];
    int mid=(l+r)/2;
    if(tr<=mid)
        return ask(x*2,l,mid,tl,tr);
    if(tl>mid)
        return ask(x*2+1,mid+1,r,tl,tr);
    return max(ask(x*2,l,mid,tl,tr),ask(x*2+1,mid+1,r,tl,tr));
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
        o[i].b=read();
    for(int i=1;i<=n;i++)
        a[i]=o[i].i=read();
    for(int i=1;i<=n;i++)
        o[i].r=read();
    sort(o+1,o+1+n);
    sort(a+1,a+1+n);
    m=unique(a+1,a+1+n)-a-1;
    for(int i=1;i<=n;i++)
        o[i].i=lower_bound(a+1,a+1+m,o[i].i)-a;
    for(int i=1;i<=n;i++)
    {

        int j=i;
        while(j+1<=n&&o[j+1].b==o[j].b)
            j++;
        for(int k=i;k<=j;k++)
            if(ask(1,1,m+1,o[k].i+1,m+1)>o[k].r)
                ans++;
        for(int k=i;k<=j;k++)
            add(1,1,m+1,o[k].i,o[k].r);
        i=j;
    }
    cout<<ans;
}
12D
一眼折半搜索,但是具体实现不好实现,抄了洛谷题解
考虑枚举前一半的x,后一半的y
令bi=count(前一半ai^x),ci=count(后一半ai^x),则需要bi+ci=b1+c1
只需要用map存bi-b1
然后寻找c1-ci即可
int count(int x)
{
    int sum=0;
    while(x)
    {
        sum++;
        x=x&(x-1);
    }
    return sum;
}
map<vector<int>,int>o;
int n,a[110];
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    for(int x=(1<<15)-1;x>=0;x--)
    {
        int b1=count((a[1]>>15)^x);
        vector<int>t;
        for(int i=2;i<=n;i++)
            t.push_back(count((a[i]>>15)^x)-b1);
        o[t]=x;
    }
    for(int x=0;x<(1<<15);x++)    
    {
        int c1=count( (a[1]-(a[1]>>15<<15))^x);
        vector<int>t;
        for(int i=2;i<=n;i++)
            t.push_back(c1-count( (a[i]-(a[i]>>15<<15))^x));
        if(o.count(t))
        {
            cout<<(o[t]<<15)+x;
            return 0;
        }
    }
    cout<<-1;
}
1257F
考虑对于集合里的点,从点到"集合"连一条ai+bx的边。则为了不存在rainbow,需要新图里没有环,所以跑一跑最大生成树即可。

int n,m,a[100010],b[100010];
int fa[200010],tot;
ll ans;
struct edge
{
    int x,y,v;
    friend bool operator<(edge a,edge b)
    {
        return a.v>b.v;
    }
}e[200010];
int getfa(int x)
{
    return fa[x]==x?x:fa[x]=getfa(fa[x]);
}
int main()
{
    // freopen("1.in","r",stdin);
    m=read();
    n=read();
    for(int i=1;i<=m;i++)
        a[i]=read();
    for(int i=1;i<=n;i++)
        b[i]=read();
    for(int i=1;i<=m;i++)
    {
        for(int s=read();s;s--)
        {
            tot++;
            int x=read();
            e[tot]={i+n,x,a[i]+b[x]};
            ans+=a[i]+b[x];
        }
    }
    for(int i=1;i<=n+m;i++)
        fa[i]=i;
    sort(e+1,e+1+tot);
    for(int i=1;i<=tot;i++)
    {
        if(getfa(e[i].x)==getfa(e[i].y))
            continue;
        ans-=e[i].v;
        fa[fa[e[i].x]]=fa[e[i].y];
    }
    cout<<ans;
}
1408E
令siz表示某颜色的边的数量
则如果边的数量小于等于根号n,使用搜索找到“用这个颜色的边构成的联通块”。联通快的大小是O(siz)的。然后O(siz^2logn)地用map累加连通块内任意两点间的距离。这样最多做n/siz次,所以这种操作复杂度是O(n/siz*siz*siz*logn)=O(n根号nlogn)
如果边的数量大于根号n,那么可以暴力O(n)地跑并查集,然后对于每个询问用并查集问一问,累加答案。做一次复杂度是复杂度O((n+q)*小常数)。这样最多做O(n/siz)=O(根号n)次,所以总复杂度仍未O(n根号n*并查集的常数)
int vis[100010],u[100010],v[100010],ans[100010],n,m,fa[100010];
struct edge
{
    int x,y;
};
vector<edge>e[100010];
vector<int>a,ee[100010];
unordered_map<int,int>f[100010];
int getfa(int x)
{
    return fa[x]==x?x:fa[x]=getfa(fa[x]);
}
void dfs(int x)
{
    vis[x]=1;
    a.push_back(x);
    for(auto y:ee[x])
    {
        if(vis[y])continue;
        dfs(y);
    }
}
int main()
{
    n=read();
    m=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        e[read()].push_back({x,y});
    }
    int q=read();
    for(int i=1;i<=q;i++)
        u[i]=read(),v[i]=read();
    for(int i=1;i<=m;i++)
    {
        if(e[i].size()<=316)
        {
            for(auto j:e[i])
            {
                vis[j.x]=0;
                vis[j.y]=0;
                ee[j.x].clear();
                ee[j.y].clear();
            }
            for(auto j:e[i])
                ee[j.x].push_back(j.y),ee[j.y].push_back(j.x);
            for(auto j:e[i])
                if(vis[j.x]==0)
                {
                    a.clear();
                    dfs(j.x);
                    for(int x=0;x<a.size();x++)
                        for(int y=x+1;y<a.size();y++)
                            f[a[x]][a[y]]++;
                }
        }
        else
        {
            for(int j=1;j<=n;j++)
                fa[j]=j;
            for(auto j:e[i])
            {
                if(getfa(j.x)==getfa(j.y))
                    continue;
                fa[fa[j.x]]=fa[j.y];
            }
            for(int j=1;j<=q;j++)
                if(getfa(u[j])==getfa(v[j]))
                    ans[j]++;
        }
    }
    for(int i=1;i<=q;i++)
        printf("%d\n",ans[i]+f[u[i]][v[i]]+f[v[i]][u[i]]);
}
506D
把玩一下样例,可以发现当所有长为k的子串,2^k种情况全都出现过,则输出no。
所以容易想到k很大的时候,2^k不能全部出现,输出yes即可
否则k比较小时,需要扫一下判断no。
因此k那么大没啥用,前几位贪心地选0,因为需求的是最小字典序。到了后20位时谨慎一点。
我们把前缀全为1的长为k的后缀放在set里,然后判断第i位是0还是1,只需扫一遍set
复杂度O(nlog^2)
int n,k,vis[2000000],sum[1000010];
char s[1000010];
set<int>o,oo;
void work()
{
    n=read();k=read();
    scanf("%s",s+1);
    if(k<=20&&(1<<k)<=(n-k+1))//只有k比较小,2^k都出现了才无解
    {
        int sum=0;
        for(int i=0;i<(1<<k);i++)
            vis[i]=0;
        for(int i=1;i+k-1<=n;i++)
        {
            int t=0;
            for(int j=i;j<i+k;j++)
                t=t*2+s[j]-'0';
            vis[t]=1;
        }
        for(int i=0;i<(1<<k);i++)
            sum+=vis[i];
        if(sum==(1<<k))
        {
            printf("NO\n");
            return ;
        }
    }
    for(int i=1;i<=n;i++)
        sum[i]=sum[i-1]+(s[i]=='0');
    printf("YES\n");
    for(int i=1;i<=k-20;i++)//前几位大部分是0
        printf("0");
    int len=max(0,k-20),res=k-len;//前len位已确定后res位未确定
    o.clear();
    for(int i=len+1;i+res-1<=n;i++)
    {
        if(sum[i-1]-sum[i-len-1])
            continue;
        int t=0;
        for(int j=i;j<=i+res-1;j++)
            t=t*2+s[j]-'0';
        o.insert(t);
    }
    for(int i=res-1;i>=0;i--)//确定从右往左第i位
    {
        int sum=0;
        for(auto x:o)
        {
            if(x&(1<<i))
                sum++;
        }
        oo.clear();
        if(sum==(1<<i))
        {
            printf("1");
            for(auto x:o)
                if((x&(1<<i))==0)
                    oo.insert(x);
        }
        else
        {
            printf("0");
            for(auto x:o)
                if((x&(1<<i)))
                    oo.insert(x-(1<<i));
        
        }
        swap(oo,o);
        
    }
    printf("\n");
}
int main()
{
    // freopen("1.in","r",stdin);
    // freopen("2.out","w",stdout);
    for(int q=read();q;q--)
        work();
}
1469E
首先D1是一个比较显然的动态规划。在此基础上,把这个f数组输出一下,发现第i行的分母可以变为2^(i-1),此时分母遵循一个类似组合数的f[i][j]=f[i-1][j]+f[i-1][j-1]。
ll mod=1e9+7;
ll n,m,k,f[2010][2010];
void work()
{
    n=read();m=read();k=read();
    printf("%lld\n",f[n][n-m]*k%mod);
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int i=1;i<=2000;i++)
        for(int j=0;j<=2000;j++)
            if(j==0)
                f[i][j]=(f[i-1][j]+1)%mod;
            else
                f[i][j]=(f[i-1][j]+f[i-1][j-1])*500000004%mod;
    for(int q=read();q;q--)
        work();
}

找规律代码

int c(int m,int n)
{
    
    int C=1;
    if (m>n-m) m=n-m;
    for (int i=1;i<=m;i++)
        C = (n-i+1) * C / i;    
    return C;
}
ll mod=1e9+7;
ll n,m,k,a[2010][2010],b[2010][2010];
int main()
{
    // freopen("1.in","r",stdin);
    for(int i=1;i<=10;i++)
    {
        for(int j=0;j<=i;j++)
        {
            if(j==0)
            {

                
                b[i][j]=1<<(i-1);
                a[i][j]=b[i][j]*i;
            }
            else
            {
                a[i][j]=a[i-1][j]+a[i-1][j-1];
                b[i][j]=b[i-1][j]*2;
                if(a[i][j]==0)
                    b[i][j]=b[i][j-1];
            }
            // cout<<a[i][j]<<'/'<<b[i][j]<<' ';
            printf("%4lld ",a[i][j]);
        }
        cout<<endl;
    }
    for(int i=1;i<=10;i++)
    {
        for(int j=0;j<=i;j++)
        {
            int sum=0;

            for(int ii=1;ii<=i-j;ii++)
                sum=sum+ii*pow(2,ii-1)*c(i-ii-j,i-ii-1);
            if(j==0)
                printf("%4d ",i*(int)pow(2,i-1));
            else
                printf("%4d ",sum);
        }
        cout<<endl;
    }
}
找到规律后考虑用组合数O(nlogn)地计算每一项即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int read()
{
    int x;scanf("%d",&x);
    return x;
}
ll mod=1e9+7,g[1000010],f[1000010];
ll c(ll n,ll m)
{
    return f[m]*g[n]%mod*g[m-n]%mod;
}
ll quick(ll a,ll b)
{
    ll t=1;
    while(b)
    {
        if(b&1)
            t=t*a%mod;
        a=a*a%mod;
        b=b/2;
    }
    return t;
}
ll n,m,k,ans;
void work()
{
    n=read();m=read();k=read();
    m=n-m;ans=0;
    if(m==0)
    {
        printf("%lld\n",n*k%mod);
        return ;
    }
    for(int i=1;i<=n-m;i++)
        ans=(ans+i*quick(2,i-1)%mod*c(n-i-m,n-i-1)%mod)%mod;
    ans=ans*quick(quick(2,n-1),mod-2)%mod;
    printf("%lld\n",ans*k%mod);
}
int main()
{
    // freopen("1.in","r",stdin);
    f[0]=1;
    for(int i=1;i<=1000000;i++)
        f[i]=f[i-1]*i%mod;
    g[1000000]=quick(f[1000000],mod-2);
    for(int i=999999;i>=0;i--)
        g[i]=g[i+1]*(i+1)%mod;
    for(int t=read();t;t--)
        work();
}
1628D2
考虑对a排序,选择第一个,与23中的一个、45中的一个、67中的一个...
这样即可保证选择的n/2+1个里满足a的要求
然后相邻两个里选时,选择b较大的即可。
struct node
{
    int a,b,i;
    friend bool operator <(node a,node b)
    {
        return a.a>b.a;
    }
}o[100010];
int n;
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
        o[i]={read(),0,i};
    for(int i=1;i<=n;i++)
        o[i].b=read();
    sort(o+1,o+1+n);
    cout<<n/2+1<<'\n'<<o[1].i<<' ';
    for(int i=2;i<=n;i+=2)
    {
        if(o[i].b<=o[i+1].b)
            cout<<o[i+1].i<<' ';
        else
            cout<<o[i].i<<' ';
    }
}
798D
对每个点连0 i a[i]
对每条边连i+n,n+m+1,z和x i+n inf,y i+n inf
最小割的定义是删边的最小花费使得s到t不连通
因为点到边的边权是inf
所以s到t不连通要么s到点不连通,要么边到t不连通了。
s到点不连通等价于不使用这个点,付出了ai的代价
边到t不连通等价于不用这条边,也付出z的代价
所以用Σz减去付出的代价即可。

struct edge
{
    int y;
    ll dis;
    int next;
}e[20010];
int head[2010],tot,d[2010],m,n,used[2010];
ll ans;
void add(int x,int y,int v)
{
    tot++;
    e[tot]={y,v,head[x]};
    head[x]=tot;
    tot++;
    e[tot]={x,0,head[y]};
    head[y]=tot;
}
bool bfs()
{
    int x;
    queue<int>q;
    q.push(0);
    memset(d,0,sizeof(d));
    d[0]=1;
    while(q.size())
    {
        x=q.front();
        q.pop();
        for(int i=head[x];i;i=e[i].next)
        {
            if(e[i].dis!=0&&d[e[i].y]==0)
            {
                d[e[i].y]=d[x]+1;
                q.push(e[i].y);
            }
        }
    }
    return d[n+m+1]!=0;
}
ll dfs(ll x,ll in)
{
    ll out=0,i,k;
    if(x==n+m+1)
        return in;
    for(i=used[x];i&&in!=0;i=e[i].next,used[x]=i)
    {
        if(e[i].dis!=0&&d[e[i].y]==d[x]+1)
        {
            k=dfs(e[i].y,min(e[i].dis,in));
            e[i].dis-=k;
            e[i^1].dis+=k;
            in-=k;
            out+=k;
        }
    }
    if(out==0)
        d[x]=0;
    return out;
}
int main()
{
    // freopen("1.in","r",stdin);
    tot=1;
    n=read();m=read();
    for(int i=1;i<=n;i++)
        add(0,i,read());
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read(),z=read();
        add(i+n,n+m+1,z);
        add(x,i+n,1e9);
        add(y,i+n,1e9);
        ans+=z;
    }
    while(bfs())
    {
        memcpy(used,head,sizeof(head));
        ans-=dfs(0,1e18);
    }
    cout<<ans;
}
1082G
我首先绞尽脑汁写了一个dp
c[i]是b[i]的前缀和。
f[i]表示第i位置是最后一次使用b[i]=a[i]的位置
则答案是Σf[i],转移时本来f[i]=Σf[j],但是如果存在b[j]-c[j]==b[i]-c[i],则可以将f[j]清空
      f[0]=1;
    for(int i=1;i<=n;i++)
    {
        f[i]=0;
        for(int j=0;j<i;j++)
        {
            f[i]+=f[j];
            if(b[j]-c[j]==b[i]-c[i])
                f[j]=0;
        }
    }
    for(int i=0;i<=n;i++)
    {
        cout<<f[i]<<' ';
        ans+=f[i];
    }
    cout<<endl;
    cout<<ans<<endl;
在这个基础上加一点优化和取模即可

ll mod=1e9+7;
ll n,c[200010],b[200010],sum;
map<ll,ll>o;
void work()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        b[i]=read();
        c[i]=c[i-1]+b[i];
    }
    o.clear();
    sum=1;
    o[0]=1;
    for(int i=1;i<=n;i++)
    {
        if(o[b[i]-c[i]])
        {
            ll t=sum;
            sum=((sum+sum-o[b[i]-c[i]])%mod+mod)%mod;
            o[b[i]-c[i]]=t;
        }
        else
        {
            o[b[i]-c[i]]=sum;
            sum=sum*2%mod;
        }
    }
    cout<<(sum+mod)%mod<<'\n';

}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1485F
考虑线段树
为了不让区间出现abc,mid到mid+1左右两边总要进行分工的
要么左边没有a,此时压力落在了右边,右边不能有abc
要么左边没有abc,此时右边不能有c
要么左边没有ab,右边没有bc
你要问左边没有b,右边没有b不是也行
那我只能说你dp学得不深

    a[x]=a[x*2]+a[x*2+1];
    b[x]=b[x*2]+b[x*2+1];
    c[x]=c[x*2]+c[x*2+1];
    ab[x]=min(ab[x*2]+b[x*2+1],a[x*2]+ab[x*2+1]);
    bc[x]=min(bc[x*2]+c[x*2+1],b[x*2]+bc[x*2+1]);
int n,q,a[400010],b[400010],c[400010],ab[400010],bc[400010],abc[400010];
char s[100010];
int minnn(int x,int y,int z)
{
    x=min(x,y);
    return min(x,z);
}
void pushup(int x)
{
    a[x]=a[x*2]+a[x*2+1];
    b[x]=b[x*2]+b[x*2+1];
    c[x]=c[x*2]+c[x*2+1];
    ab[x]=min(ab[x*2]+b[x*2+1],a[x*2]+ab[x*2+1]);
    bc[x]=min(bc[x*2]+c[x*2+1],b[x*2]+bc[x*2+1]);
    abc[x]=minnn(a[x*2]+abc[x*2+1],ab[x*2]+bc[x*2+1],abc[x*2]+c[x*2+1]);
}
void build(int x,int l,int r)
{
    if(l==r)
    {
        if(s[l]=='a')
            a[x]=1;
        if(s[l]=='b')
            b[x]=1;
        if(s[l]=='c')
            c[x]=1;
        return ;
    }
    int mid=(l+r)/2;
    build(x*2,l,mid);
    build(x*2+1,mid+1,r);
    pushup(x);
}
void add(int x,int l,int r,int d)
{
    if(l==r)
    {
        a[x]=b[x]=c[x]=0;
        if(s[l]=='a')
            a[x]=1;
        if(s[l]=='b')
            b[x]=1;
        if(s[l]=='c')
            c[x]=1;
        return ;
    }
    int mid=(l+r)/2;
    if(d<=mid)
        add(x*2,l,mid,d);
    else
        add(x*2+1,mid+1,r,d);
    pushup(x);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();q=read();
    scanf("%s",s+1);
    build(1,1,n);
    for(;q;q--)
    {
        int x=read();
        scanf(" %c",&s[x]);
        add(1,1,n,x);
        printf("%d\n",abc[1]);
    }
}
1609E
线性基
考虑离线地做,对询问排序
那么当有了i个数字时,问能不能拼出来x
维护一个数组表示能拼出来的数字,sum表示用了几个数字
那么新来一个数字,如果原来拼不出来,就要加进线性基里,然后重新跑f数组

求答案时,如果拼不出来显然是0,否则答案是2^(i-sum)。因为线性基以外的数字任意取或者不取,方案数是这么多。每种取得方案,用线性基这sum个数字有且仅有一组方案使得异或和恰好为x
int n,q,f[23][2000000],sum,a[100010];
ll ans=1,mod=1e9+7;
struct node
{
    int i,x,ans,pos;
    friend bool operator <(node a,node b)
    {
        return a.i<b.i;
    }
}o[100010];
bool pos_(node a,node b)
{
    return a.pos<b.pos;
}
int main()
{
    // freopen("1.in","r",stdin);
    f[0][0]=1;
    n=read();q=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    for(int i=1;i<=q;i++)
    {
        o[i].i=read();
        o[i].x=read();
        o[i].pos=i;
    }
    sort(o+1,o+1+q);
    ans=1;
    for(int i=1;i<=q;i++)
    {
        for(int j=o[i-1].i+1;j<=o[i].i;j++)
        {
            if(f[sum&1][a[j]]){
                ans=ans*2%mod;
                continue;
            }
            for(int y=0;y<(1<<20);y++)
            {
                if(f[sum&1][y])
                    f[(sum&1)^1][y]=f[(sum&1)^1][y^a[j]]=1;
            }
            sum++;
        }
        if(f[sum&1][o[i].x])
            o[i].ans=ans;

    }
    sort(o+1,o+1+q,pos_);
    for(int i=1;i<=q;i++)
        printf("%d\n",o[i].ans);
}
959F
众所周知,欧拉函数是积性函数。若 n = p^k,其中 p 是质数,那么 phi(n) = p^k - p^(k - 1)。
所以对每个质数分别计算贡献,复杂度62qlogn。理论存在,开始卡常!
维护区间乘积是p的几次方,则区间乘p^k等价于区间加k,询问是区间求和。
首先把线段树变成了树状数组,后来树状数组常数又优化了一点点,最后发现:维护k的几次方时,不需要对mod-1取模,因为最坏情况下是ai=256且进行了
q-1次1到n区间乘256,此时最大也就qnlog(300),long long存得下。最后扔掉了对mod-1取模就过了
快读快写什么的都整上了哈哈哈哈

typedef long long ll;
char buf[1<<15],*fs,*ft;
inline char getc(){
  return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:* fs++;
}
inline int read(){
    int This=0,F=1; char ch=getc();
    while(ch<'0'||ch>'9'){
        if(ch=='-') F=-1;
        ch=getc();
    }
    while(ch>='0'&&ch<='9'){
        This=(This<<1)+(This<<3)+ch-'0';
        ch=getc();
    }
    return This*F;
}
inline void write(int x)
{
    if(x==0)
    {
        putchar('0');
        return;
    }
    int num=0;char ch[16];
    while(x) ch[++num]=x%10+'0',x/=10;
    while(num) putchar(ch[num--]);
    putchar('\n');
}
ll mod=1e9+6,mod1=1e9+7;
ll n,C[400010],D[400010];
ll quick(ll a,ll b)
{
    if(b==-1)
        return 0;
    ll t=1;
    while(b)
    {
        if(b&1)
            t=t*a%mod1;
        a=a*a%mod1;
        b=b/2;
    }
    return t;
}
pair<ll,ll> sum(int i)
{
    pair<ll,ll> res={0,0};
    while(i>0)
    {
        res.first+=C[i];
        res.second+=D[i];
        i-=i&-i;
    }
    return res;
}
void update(int i,ll v1,ll v2)
{
    while(i<=n+1)
    {
        C[i]+=v1;
        D[i]+=v2;
        i+=i&-i;
    }
}
ll ask(int tl,int tr)
{
    pair<ll,ll>a,b;
    a=sum(tr+1);
    b=sum(tl-1);
    return a.first*(tr+1)-a.second-1ll*b.first*tl+b.second;
}
void add(int tl,int tr,int v)
{
    update(tl,v,v*tl);
    update(tr+1,-v,-v*(tr+1));
}
int q,num[400010],l[400010],r[400010],v[400010],a[400010];
char s[400010];
ll ans[400010],t[400010];
vector<int>pri;
int main()
{
    for(int i=2;i<=300;i++)
    {
        int flag=1;
        for(int j=2;j<i;j++)
            if(i%j==0)
                flag=0;
        if(flag==1)
            pri.push_back(i);
    }
    n=read();q=read();
    for(int i=1;i<=n;i++)
        num[i]=read();
    for(int i=1;i<=q;i++)
    {
        s[i]=getc();
        if(s[i]=='T')
            for(int j=1;j<=6;j++)
                getc();
        else
            for(int j=1;j<=7;j++)
                getc();
        if(s[i]=='T')
            l[i]=read(),r[i]=read();
        else
            l[i]=read(),r[i]=read(),v[i]=read();
        ans[i]=1;
    }
    for(auto x:pri)
    {
        for(int i=1;i<=n+1;i++)
            C[i]=D[i]=0;
        int sum=0;
        for(int i=1;i<=n;i++)
        {
            a[i]=0;
            while(num[i]%x==0)
            {
                num[i]=num[i]/x;
                a[i]++;
                sum++;
            }
            if(a[i])
                add(i,i,a[i]);
        }
        for(int i=1;i<=q;i++) 
        {
            if(s[i]=='M')
            {
                t[i]=0;
                while(v[i]%x==0)
                {
                    t[i]++;
                    sum++;
                    v[i]=v[i]/x;
                }
            }
        }
        if(sum==0)
            continue;
        
        for(int i=1;i<=q;i++)
        {
            if(s[i]=='T')
            {
                t[i]=ask(l[i],r[i]);
                if(t[i])
                    ans[i]=ans[i]*quick(x,t[i]-1)%mod1*(x-1)%mod1;
            }
            else if(t[i])
                add(l[i],r[i],t[i]);
        }
    }
    
    for(int i=1;i<=q;i++)
        if(s[i]=='T')
            write(ans[i]);
}
1114F
额啊,还以为每个点有自己的值,原来是每个数字只出现一次
所以sb题
考虑给每个点加一
令ans[i]表示至少包含1到i的,那么容斥一下,仅包含1到i的方案数=包含1到i+1的-包含1到i的
考虑包含1到i-1的链左右端点是lr,那么i有几种情况:
1、i是l的子节点且必须和r的lca为1
2、i是r的子节点且必须和l的lca为1
以上的反例是1 22 342。原来的链是1 3 ,4来了之后可以发现没有包含1到4的链
3、i是l的父亲或者i是r的父亲,此时不需要更改
4、类似反例,i位于l到r路径上某个点的另一个子树里,此时可以break
如果普通情况,lr没有1的话,方案数显然是siz[l]*siz[r]
如果存在1的话,假设l是1,另一边仍然是siz[r],1这边是n-l的祖先的siz
mex=0的话,答案是n*(n-1)/2-包含0的方案数。包含0的方案数扫一遍根节点的出边即可。
int n,siz[200010],d[200010],fa[200010][22];
ll ans[200010];
vector<int>e[200010];
void dfs(int x)
{
    siz[x]=1;
    for(auto y:e[x])
    {
        if(fa[x][0]==y)continue;
        fa[y][0]=x;
        d[y]=d[x]+1;
        dfs(y);
        siz[x]+=siz[y];
    }
}
int lca(int x,int y)
{
    if(d[x]>d[y])
        swap(x,y);
    for(int i=20;i>=0;i--)
    {
        if(d[fa[y][i]]>=d[x])
            y=fa[y][i];
    }
    if(x==y)
        return x;
    for(int i=20;i>=0;i--)
    {
        if(fa[x][i]!=fa[y][i])
            x=fa[x][i],y=fa[y][i];

    }
    return fa[x][0];
}
void work()
{
    n=read();
    for(int i=1;i<=n+1;i++)
    {
        siz[i]=0;
        fa[i][0]=0;
        e[i].clear();
        d[i]=0;
        ans[i]=0;
    }
    for(int i=1;i<n;i++)
    {
        int x=read()+1,y=read()+1;
        e[x].push_back(y);
        e[y].push_back(x);
    }

    d[1]=1;
    dfs(1);
    for(int i=1;i<=20;i++)
        for(int x=1;x<=n;x++)
            fa[x][i]=fa[fa[x][i-1]][i-1];
    ll sum=1;
    for(auto y:e[1])
    {
        ans[1]+=siz[y]*sum;
        sum+=siz[y];
    }
    int l=1;int r=1;
    int il,ir,sl=-1,sr=-1;
    for(int i=2;i<=n;i++)
    {
        il=lca(i,l),ir=lca(i,r);
        if(il==l&&ir==1)
        {
            if(l==1)
            {
                sl=siz[i];
                for(int j=i;fa[j][0]!=1;j=fa[j][0],sl=siz[j]);
            }
            l=i;
        }
        else if(ir==r&&il==1)
        {
            if(r==1)
            {
                sr=siz[i];
                for(int j=i;fa[j][0]!=1;j=fa[j][0],sr=siz[j]);
            }
            r=i;
        }
        else if(ir!=i&&il!=i)
            break;
        if(l==1)
            ans[i]=1ll*siz[r]*(n-sr);
        else if(r==1)
            ans[i]=1ll*siz[l]*(n-sl);
        else
            ans[i]=1ll*siz[l]*siz[r];
    }
    cout<<n*(n-1ll)/2-ans[1]<<' ';
    for(int i=2;i<=n+1;i++)
        cout<<ans[i-1]-ans[i]<<' ';
    cout<<'\n';
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1527D
写了个不知道叫啥的线段树
主旨是先扫一遍所有询问,把每个颜色以后最多能加多少sum[i],给算出来
接下来每次增加时,我先把这个sum全给你。在颜色增加时,就相当于未来能增加的变少了,sum减一下。
询问或者换颜色时,给还没加上的全部夺走,就相当于没加上嘛,于是只加上了这段时间内的

struct node
{
    char s[6];
    int l,r,c,x,i;
}o[4000010];
int c[4000010],n,q,flag[4000010];
ll a[4000010],sum[4000010],lazy[4000010];
void build(int x,int l,int r)
{
    c[x]=1;
    if(l==r){
        a[l]=sum[1];
        return ;
    }
    int mid=(l+r)/2;
    build(x*2,l,mid);
    build(x*2+1,mid+1,r);
}
void pushdown(int x)
{
    lazy[x*2]=lazy[x*2]+lazy[x];
    lazy[x*2+1]=lazy[x*2+1]+lazy[x];
    lazy[x]=0;
    c[x*2]=c[x];
    c[x*2+1]=c[x];    
    flag[x*2]=1;
    flag[x*2+1]=1;
    flag[x]=0;
}
void add(int x,int l,int r,int tl,int tr,int col)
{
    if(tl<=l&&r<=tr&&c[x])
    {
        lazy[x]=lazy[x]-sum[c[x]]+sum[col];
        c[x]=col;
        flag[x]=1;
        return ;
    }
    if(flag[x])
        pushdown(x);
    int mid=(l+r)/2;
    if(tl<=mid)
        add(x*2,l,mid,tl,tr,col);
    if(tr>mid)
        add(x*2+1,mid+1,r,tl,tr,col);
    if(c[x*2]==c[x*2+1])
        c[x]=c[x*2];
    else
        c[x]=0;
}
int work(int x,int l,int r,int d)
{
    if(l==r){
        a[l]+=lazy[x];
        lazy[x]=0;
        return c[x];
    }
    if(flag[x])
        pushdown(x);
    int mid=(l+r)/2;
    if(d<=mid)
        return work(x*2,l,mid,d);
    else
        return work(x*2+1,mid+1,r,d);

}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();q=read();
    for(int i=1;i<=q;i++)
    {
        scanf("%s",o[i].s);
        if(o[i].s[0]=='C')
        {
            o[i].l=read();
            o[i].r=read();
            o[i].c=read();
        }
        if(o[i].s[0]=='A')
        {
            o[i].c=read();
            o[i].x=read();
            sum[o[i].c]+=o[i].x;
        }
        else
            o[i].i=read();
    }
    build(1,1,n);
    for(int i=1;i<=q;i++)
    {
        if(o[i].s[0]=='C')
            add(1,1,n,o[i].l,o[i].r,o[i].c);
        else if(o[i].s[0]=='A')
            sum[o[i].c]-=o[i].x;
        else
        {
            int color=work(1,1,n,o[i].i);
            cout<<a[o[i].i]-sum[color]<<'\n';
        }
    }
}
1638E
考虑如果x不等于y,则从x向y连边,表示x如果选了人,y就不能选猫,也必须选人
则从1跑一遍有向图强连通分量。如果发现sum[cnt]==n,则证明所有点处于一个强联通分量里,则不存在合法方案,输出no
否则,考虑缩点后的有向无环图,把没有出边的那个强联通分量取人,其他强联通分量取猫即可
可以发现只从1跑一下tarjan即可
int dfn[1000010],low[1000010];
int num,c[1000010],ins[1000010],n,m,cnt,sum[1000010];
vector<int>e[1000010];
stack<int>q;
void tarjan(int x)
{
    dfn[x]=low[x]=++num;
    q.push(x);
    ins[x]=1;
    for(auto y:e[x])
    {
        if(!dfn[y])
        {
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(ins[y])
        {
            low[x]=min(low[x],low[y]);
        }
    }
    if(dfn[x]==low[x])
    {
        cnt++;
        int y;
        do{
            y=q.top();
            q.pop();
            ins[y]=0;
            c[y]=cnt;
            sum[cnt]++;
        }while(x!=y);
    }
}
void work()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        dfn[i]=low[i]=0;
        c[i]=0;
        sum[i]=0;
        e[i].clear();
    }
    num=0;
    cnt=0;
    for(int i=1;i<=m;i++)
    {
        int x=read();int y=read();
        if(x!=y)
            e[x].push_back(y);
    }
    tarjan(1);
    if(sum[cnt]==n)
        printf("No\n");
    else
    {
        printf("Yes\n");
        cout<<sum[1]<<' '<<n-sum[1]<<'\n';
        for(int i=1;i<=n;i++)
            if(c[i]==1)
                cout<<i<<' ';
        cout<<'\n';
        for(int i=1;i<=n;i++)
            if(c[i]!=1)
                cout<<i<<' ';
        cout<<'\n';
    }
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1239D
stl大师
首先理解题意。题目不允许把一个单词中的一段替换成另一个单词,只允许整个单词整个地替换。所以显然是一个最短路问题
先把单词读进来,然后反向建边,跑多源最短路,维护每个点最少几个r,保证r数量最少的同时,最少长为几。最后扫一遍原来的n个单词。如果在图里出现过,那就拿着d更新答案。否则只能不动,拿着他自己更新答案。
记得第二维开ll
pair<int,ll>ask(string s)
{
    pair<int,ll>t={0,s.size()};
    for(auto x:s)
        if(x=='r')
            t.first++;
    return t;
}
void work(string &s)
{
    for(auto &x:s)
        if('A'<=x&&x<='Z')
            x=x-'A'+'a';
}
int n,cnt,m,vis[100010];
priority_queue<pair<pair<int,ll>,int>,vector<pair<pair<int,ll>,int>>,greater<pair<pair<int,ll>,int>>>q;
pair<int,ll>d[100010];
map<string,int>o;
string a[100010],s[100010];
vector<int>e[100010];
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++){
        cin>>a[i];
        work(a[i]);
    }
    m=read();
    for(int i=1;i<=m;i++)
    {
        string x,y;
        cin>>x>>y;
        work(x);
        work(y);
        if(o[x]==0)
        {
            cnt++;
            o[x]=cnt;
            s[cnt]=x;
        }
        if(o[y]==0)
        {
            cnt++;
            o[y]=cnt;
            s[cnt]=y;
        }
        e[o[y]].push_back(o[x]);

    }
    for(int i=1;i<=cnt;i++)
    {
        d[i]=ask(s[i]);
        q.push({d[i],i});
    }
    while(q.size())
    {
        int x=q.top().second;
        q.pop();
        if(vis[x])
            continue;
        vis[x]=1;
        for(auto y:e[x])
        {
            if(d[x]<d[y])
            {
                d[y]=d[x];
                q.push({d[x],y});
            }
        }
    }
    pair<int,ll>ans={0,0},t;
    for(int i=1;i<=n;i++)
    {
        if(o[a[i]])
            t=d[o[a[i]]];
        else
            t=ask(a[i]);
        ans.first+=t.first;
        ans.second+=t.second;
    }
    cout<<ans.first<<' '<<ans.second;
}
467D
挺简单的,为啥2400
考虑受到伤害当且仅当大于等于b的点的数量大于等于a
如果小于a,显然输出0
否则我们对大于等于b的怪物算一下概率为(cnt-a)/cnt
对于小于b的怪物算一下概率为(cnt+1-a)/(cnt+1)
前缀和一下子即可
ll mod=998244353;
ll quick(ll a,ll b)
{
    ll t=1;
    while(b)
    {
        if(b&1)
            t=t*a%mod;
        a=a*a%mod;
        b=b/2;
    }
    return t;
}
ll n,m,d[200010],sum[200010],f[200010],ans;
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
        d[i]=read();
    sort(d+1,d+1+n);
    for(int i=1;i<=n;i++)
        sum[i]=(sum[i-1]+d[i])%mod;
    for(int i=1;i<=n+1;i++)
        f[i]=quick(i,mod-2);
    for(;m;m--)
    {
        ll a=read();ll b=read();
        int cnt=lower_bound(d+1,d+1+n,b)-d-1;
        cnt=n-cnt;
        if(cnt<a)
            cout<<"0\n";
        else
        {
            ans=((sum[n]-sum[n-cnt])%mod+mod)%mod*(cnt-a)%mod*f[cnt]%mod;
            ans=ans+sum[n-cnt]*(cnt+1-a)%mod*f[cnt+1]%mod;
            cout<<ans%mod<<'\n';
         }
    }
}
1418E
首先他有来有回,并非有向无环图,所以不能拓扑排序地做,我们需要高斯消元。
但是复杂度接受不了。把每一行分开来做,然后内部是一个
2 -1 0 0 0 0
-1 3 -1 0 0 0
0 -1 3 -1 0 0
0 0 -1 3 -1 0
0 0 0 -1 3 -1
0 0 0 0 -1 2
的矩阵,可以发现是一个稀疏矩阵,从上往下再从下往上扫一扫即可。
找规律代码:
double a[1010][1010],f[1010];
int n,m,x,y;
int main()
{
    freopen("1.in","r",stdin);
    n=read();m=read();x=read();y=read();
    if(m==1)
    {
        printf("%.6lf",2.0*(n-1));
        return 0;
    }
    // for(int t=1;t<n;t++)//做n-1次
    {
        for(int i=2;i<n;i++)
        {
            a[i][i-1]=a[i][i+1]=-1;
            a[i][i]=3;
        }
        a[1][1]=2;
        a[1][2]=-1;
        a[n][n]=2;
        a[n][n-1]=-1;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
                cout<<a[i][j]<<' ';
            cout<<endl;
        }
        cout<<endl;

        // for(int i=1;i<=1;i++)
        // {
        for(int i=1;i<n;i++)
        {
            for(int j=n;j>=i;j--)
                a[i][j]/=a[i][i];
            for(int j=n;j>=i;j--)
                a[i+1][j]-=a[i][j]*(a[i][i]/a[i+1][i]);

        }
        for(int x=1;x<=n;x++)
        {
            for(int y=1;y<=n;y++)
                cout<<a[x][y]<<' ';
            cout<<endl;
        }
        cout<<endl;
        a[n][n]/=a[n][n];
        for(int i=n;i>1;i--)
            a[i-1][i]-=a[i-1][i];
        for(int x=1;x<=n;x++)
        {
            for(int y=1;y<=n;y++)
                cout<<a[x][y]<<' ';
            cout<<endl;
        }
    }
}
正解
double a[1010][1010],f[1010],b[1010];
int n,m,x,y;
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();x=read();y=read();
    if(m==1)
    {
        printf("%.6lf",2.0*(n-x));
        return 0;
    }
    n=n-(x-1);
    for(int t=1;t<n;t++)//做n-1次
    {
        for(int i=2;i<m;i++)
        {
            a[i][i-1]=a[i][i+1]=-1;
            a[i][i]=3;
            b[i]=4+f[i];
        }
        a[1][1]=2;
        a[1][2]=-1;
        b[1]=3+f[1];
        a[m][m]=2;
        a[m][m-1]=-1;
        b[m]=3+f[m];
        for(int i=1;i<m;i++)
        {
            b[i]/=a[i][i];
            for(int j=i+1;j>=i;j--)
                a[i][j]/=a[i][i];
            b[i+1]-=b[i]*(a[i][i]/a[i+1][i]);
            for(int j=i+1;j>=i;j--)
                a[i+1][j]-=a[i][j]*(a[i][i]/a[i+1][i]);
        }
        b[m]/=a[m][m];
        a[m][m]/=a[m][m];
        for(int i=m;i>1;i--)
        {
            b[i-1]-=b[i]*a[i-1][i];
            a[i-1][i]-=a[i-1][i];
        }
        for(int i=1;i<=m;i++)
            f[i]=b[i];
    }
    printf("%.6lf",f[y]);
}
24D
看到n/3<k<k*2/3,鸽巢定理告诉我们,询问i,i+1,i+2三三一组,一定存在一个1和一个0。
不妨设b[1],b[2],b[3]是0,b[4].b[5],b[6]是1,那么从123过渡到456,总有一次是从0变到1的,那一次就可以确定前面的是0,后面的是1。
于是再询问一下b[2],b[3],b[4]、b[3],b[4],b[5],例如b[2],b[3],b[4]是1,则证明b[1]是0,b[4]是1
有了一个0位置和一个1位置,考虑对每个三三一组再使用两次询问确定答案。
如果0位置或1位置属于当前三三一组,则使用0+1+未知的方式得到未知数的值。
如果不属于,我们可以利用上之前询问的结果。
如果这三个0居多,那么询问前两个+1:如果得到1证明这两个里一定一个1一个0,第三个一定是0。只需要询问第一个+1+0,即可确定第一个和第二个;如果得到0证明这两个一定两个0,只需要询问第三个+1+0确定第三个即可。
如果这三个1居多,反过来做如上操作。

#include<bits/stdc++.h>
using namespace std;
int read()
{
    int x;scanf("%d",&x);return x;
}
int ask(int x,int y,int z)
{
    cout<<"? "<<x<<' '<<y<<' '<<z<<endl;
    return read();
}
int sum,n,ans[10010],b[10],pos[2],a[10010];
void work()
{
    n=read();
    for(int i=1;i<=n;i++)
        ans[i]=-1;
    for(int i=1;i<=n;i+=3)
        a[i]=ask(i,i+1,i+2);
    for(int i=1;i<=n;i+=3)
    {
        if(a[i]==0&&a[i+3]==1)
        {
            b[1]=i;
            b[2]=i+1;
            b[3]=i+2;
            b[4]=i+3;
            b[5]=i+4;
            b[6]=i+5;
            break;
        }
        if(a[i]==1&&a[i+3]==0)
        {
            b[1]=i+5;
            b[2]=i+4;
            b[3]=i+3;
            b[4]=i+2;
            b[5]=i+1;
            b[6]=i;
            break;
        }
    }
    if(ask(b[2],b[3],b[4])==1){
        pos[0]=b[1];
        pos[1]=b[4];
    }
    else if(ask(b[3],b[4],b[5])==1){
        pos[0]=b[2];
        pos[1]=b[5];
    }
    else{
        pos[0]=b[3];
        pos[1]=b[6];
    }
    ans[pos[0]]=0;
    ans[pos[1]]=1;
    for(int i=1;i<=n;i+=3)
    {
        if(i==pos[0]||i==pos[1]||i+1==pos[0]||i+1==pos[1]||i+2==pos[0]||i+2==pos[1])
        {
            for(int j=i;j<i+3;j++)
                if(ans[j]==-1)
                    ans[j]=ask(j,pos[0],pos[1]);
            continue;
        }
        if(a[i]==0)
        {
            if(ask(pos[1],i,i+1)==1)//i和i+1存在一个1,则i+2一定为0
            {
                ans[i+2]=0;
                ans[i]=ask(pos[1],pos[0],i);//询问i是1还是0
                ans[i+1]=1-ans[i];
            }
            else//两个都为0
            {
                ans[i]=ans[i+1]=0;
                ans[i+2]=ask(pos[1],pos[0],i+2);//询问i+1
            }
        }
        else
        {
            if(ask(pos[0],i,i+1)==0)//i和i+1存在一个0
            {
                ans[i+2]=1;
                ans[i]=ask(pos[1],pos[0],i);
                ans[i+1]=1-ans[i];
            }
            else
            {
                ans[i]=ans[i+1]=1;
                ans[i+2]=ask(pos[1],pos[0],i+2);
            }
        }
    }
    sum=0;
    for(int i=1;i<=n;i++)
        if(ans[i]==0)
            sum++;
    cout<<"! "<<sum<<' ';
    for(int i=1;i<=n;i++)
        if(ans[i]==0)
            cout<<i<<' ';
    cout<<endl;
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1617D2
考虑动态规划,f[i]表示以i作为递增的结尾,此时的递减的结尾最大是多少
则如果a[i]>a[i-1],f[i]可以继承f[i-1]
否则如果a[i]>g[i-1],可以让a[i-1]做递减的结尾,我做递增的结尾,此时f[i]=a[i-1]
对两者取max,这样贪心地让后续能走得更远。
输出方案数则需要dp时记录答案,回溯回去。
g的动态规划与之类似
int f[200010],g[200010],a[200010],ans[200010],pref[200010],preg[200010];
int n;
int main()
{
    // f[i]表示以i为递增的结尾,递减的结尾最大是多少
    f[1]=3e5;
    g[1]=-1;
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    for(int i=2;i<=n;i++)
    {
        f[i]=-1;
        g[i]=3e5;
        if(a[i]>a[i-1]){
            f[i]=f[i-1];
            pref[i]=0;
        }
        if(a[i]>g[i-1]&&a[i-1]>=f[i]){
            f[i]=a[i-1];
            pref[i]=1;
        }
        if(a[i]<a[i-1]){
            g[i]=g[i-1];
            preg[i]=1;
        }
        if(a[i]<f[i-1]&&a[i-1]<=g[i]){
            g[i]=a[i-1];
            preg[i]=0;
        }

    }
    if(f[n]==-1&&g[n]==300000)
    {
        cout<<"No";
        return 0;
    }
    cout<<"Yes\n";
    int pos=n,now;
    if(f[n]!=-1)
        now=0;
    else
        now=1;
    ans[n]=now;
    for(;pos!=0;pos--)
    {
        ans[pos]=now;
        if(now==1)
            now=preg[pos];
        else
            now=pref[pos];
    }
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<' ';
}
1144G
考虑存在长为奇数的回文路径时只需要存在xy有边且yx有边
长为偶数的回文路径只需要xy有边且yx有边且两条边的边权相同
于是使用map整一整即可
int n,m;
map< pair<int,int>,int >sum;
int cnt[2];
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        char c;
        cin>>c;
        
        if(c=='+')
        {
            int x=read(),y=read();
            cin>>c;
            if(sum[{y,x}])
                cnt[1]++;
            if(sum[{y,x}]==c)
                cnt[0]++;
            sum[{x,y}]=c;
        }
        else if(c=='-')
        {
            int x=read(),y=read();
            if(sum[{y,x}]==sum[{x,y}])
                cnt[0]--;
            if(sum[{y,x}])
                cnt[1]--;
            sum[{x,y}]=0;
        }
        else if(c=='?')
        {
            if(cnt[read()&1])
                cout<<"YES\n";
            else
                cout<<"NO\n";
        }
    }
}
1494E
考虑如果枚举an给b1分多少。如果分得多了,可能最后循环回来时,n的不够用。如果分得少了,可能中间a1到a[n-1]不够用。这两种情况都是非法情况,但是可以用不同返回值进行区分。
int n;
int a[1000010],b[1000010];
int ask(int v)//如果an给b1分的是v
{
    int now=v;
    for(int i=1;i<n;i++)
    {
        if(now+b[i]<a[i])
            return 1;
        now=b[i]-max(0,a[i]-now);
    }
    if(now+b[n]-v>=a[n])
        return 0;
    return -1;
}
void work()
{
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    for(int i=1;i<=n;i++)
        b[i]=read();
    int l=0,r=min(b[n],a[1]),mid;
    while(l+1<r)
    {
        mid=(l+r)/2;
        switch (ask(mid))
        {

            case -1:r=mid;break;
            case 0:cout<<"YES\n"; return ;
            case 1:l=mid;
        }
    }
    if(ask(l)==0||ask(r)==0)
        cout<<"YES\n";
    else
        cout<<"NO\n";
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1373F二分答案做法
考虑枚举ai是左边某一段的最大值,右边存在令一段可以断开的地方时,将中间断开,会对答案贡献--
所以对于ai找到1到i-1里面最靠右的大于v的下标A,i+1到n的最靠左的大于v的下标C,C的右边的最靠左的小于v的下标D,则对答案的贡献减去(i-A+1)*(D-C)
int n,a[300010];
ll ans;
int maxx[1200010],minn[1200010];
void build(int x,int l,int r)
{
    if(l==r)
    {
        maxx[x]=a[l];minn[x]=a[l];
        return ;
    }
    int mid=(l+r)/2;
    build(x*2,l,mid);
    build(x*2+1,mid+1,r);
    maxx[x]=max(maxx[x*2],maxx[x*2+1]);
    minn[x]=min(minn[x*2],minn[x*2+1]);
}
int ask1(int x,int l,int r,int tl,int tr,int v)//区间最右的大于v的下标
{
    if(tr==0)
        return 0;
    if(l==r){
        if(maxx[x]>v)
            return l;
        return 0;
    }
    int mid=(l+r)/2;
    if(tl<=l&&r<=tr)
    {
        if(maxx[x]<=v)
            return 0;
        int t=ask1(x*2+1,mid+1,r,tl,tr,v);
        if(t)
            return t;
        return ask1(x*2,l,mid,tl,tr,v);
    }
    if(tr>mid)
    {
        int t=ask1(x*2+1,mid+1,r,tl,tr,v);
        if(t)
            return t;
    }
    if(tl<=mid)
        return ask1(x*2,l,mid,tl,tr,v);
    return 0;
}

int ask2(int x,int l,int r,int tl,int tr,int v)//区间最左的大于v的下标
{
    if(tl==n+1)
        return n+1;
    if(l==r)
    {
        if(maxx[x]>v)
            return l;
        return n+1;
    }
    int mid=(l+r)/2;
    if(tl<=l&&r<=tr){
        if(maxx[x]<=v)
            return n+1;
        int t=ask2(x*2,l,mid,tl,tr,v);
        if(t!=n+1)
            return t;
        return ask2(x*2+1,mid+1,r,tl,tr,v);
    }
    if(tl<=mid)
    {
        int t=ask2(x*2,l,mid,tl,tr,v);
        if(t!=n+1)
            return t;
    }
    if(tr>mid)
        return ask2(x*2+1,mid+1,r,tl,tr,v);
    return n+1;
}
int ask3(int x,int l,int r,int tl,int tr,int v)//区间最左的小于v的下标
{
    if(tl==n+1)
        return n+1;
    if(l==r)
    {
        if(minn[x]<v)
            return l;
        return n+1;
    }
    int mid=(l+r)/2;
    if(tl<=l&&r<=tr){
        if(minn[x]>=v)
            return n+1;
        int t=ask3(x*2,l,mid,tl,tr,v);
        if(t!=n+1)
            return t;
        return ask3(x*2+1,mid+1,r,tl,tr,v);
    }
    if(tl<=mid)
    {
        int t=ask3(x*2,l,mid,tl,tr,v);
        if(t!=n+1)
            return t;
    }
    if(tr>mid)
        return ask3(x*2+1,mid+1,r,tl,tr,v);
    return n+1;
}
void work()
{
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    ans=0;
    build(1,1,n);
    for(int i=1;i<=n;i++)
    {
        ans=ans+1ll*(1+n-i)*(n-i)/2;
        ll A=ask1(1,1,n,1,i-1,a[i])+1;
        int C=ask2(1,1,n,i+1,n,a[i]);
        int D;
        if(C==n+1)
            D=n+1;
        else
            D=ask3(1,1,n,C,n,a[i]);
        ans=ans-(i-A+1)*(D-C);
    }
    cout<<ans<<'\n';
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1827B2
将所有情况枚举一下,发现看做012的同余系的话,对于每一个相邻的两行,a[i][j+1]-a[i][j]是相同的。对于每一列也同理。
因此维护f[i]其中i=1到n表示每相邻行之间的数字的差值,f[i]其中i=n+1到n+m表示相邻列之间的数字的差值。
可以发现,由于相邻的数字值不同,f[i]的取值只有1和2
则如果y1+1==y2,证明f[x2]和f[y2+n]需要不同
如果y2+1==y1,证明f[x2]和f[y1+n]需要相同
建图跑染色即可。
int n,m,f[4010];
vector<int>e[4010],ee[4010];
int dfs(int x)
{
    for(auto y:e[x])
    {
        if(f[y]==-1)
        {
            f[y]=3-f[x];
            if(dfs(y))
                return 1;
        }
        else if(f[x]==f[y])
            return 1;
    }
    for(auto y:ee[x])
    {
        if(f[y]==-1)
        {
            f[y]=f[x];
            if(dfs(y))
                return 1;
        }
        else if(f[y]+f[x]==3)
            return 1;
    }
    return 0;
}
void work()
{
    n=read();m=read();
    for(int i=1;i<=n+m+2;i++)
    {
        f[i]=-1;
        e[i].clear();
        ee[i].clear();
    }
    for(int k=read();k;k--)
    {
        int x1,x2,y1,y2;
        x1=read();y1=read();x2=read();y2=read();
        if(y1+1==y2)
        {
            e[x2].push_back(y2+n);//不同
            e[y2+n].push_back(x2);
        }
        else
        {
            ee[x2].push_back(y1+n);
            ee[y1+n].push_back(x2);
        }
    }
    for(int i=1;i<=n+m;i++)
    {
        if(f[i]==-1)
        {
            f[i]=1;
            if(dfs(i))
            {
                printf("NO\n");
                return ;
            }
        }
    }
    printf("YES\n");

}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1844E
当存在
..    
.*

..
*.

*.
..

.*
..
时,这个*就需要被覆盖为.
于是大胆dfs一下即可,把样例调过就算成功
int n,m;
char s[2010][2010];
void dfs(int x,int y)
{
    if(x*y==0||x>n||y>m||s[x][y]!='*')
        return ;
    if((s[x+1][y]=='.'&&s[x+1][y+1]=='.'&&s[x][y+1]=='.') ||(s[x-1][y]=='.'&&s[x-1][y-1]=='.'&&s[x][y-1]=='.') ||(s[x+1][y]=='.'&&s[x+1][y-1]=='.'&&s[x][y-1]=='.') ||(s[x-1][y]=='.'&&s[x-1][y+1]=='.'&&s[x][y+1]=='.'))
        s[x][y]='.';
    else
        return ;
    dfs(x+1,y-1);
    dfs(x+1,y);
    dfs(x+1,y+1);
    dfs(x,y-1);
    dfs(x,y+1);
    dfs(x-1,y-1);
    dfs(x-1,y);
    dfs(x-1,y+1);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
        scanf("%s",s[i]+1);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(s[i][j]=='*')
                dfs(i,j);        
    for(int i=1;i<=n;i++)
        printf("%s\n",s[i]+1);
}
525D RE的DFS
dfs会由于栈太大导致re
考虑使用bfs优化之
int n,m;
char s[2010][2010];
queue<pair<int,int>>q;
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
        scanf("%s",s[i]+1);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(s[i][j]=='*')
                q.push({i,j});
    while(q.size())
    {
        int x=q.front().first;
        int y=q.front().second;
        q.pop();
        if(x==0||y==0||x>n||y>m||s[x][y]!='*')
            continue;
        if((s[x+1][y]=='.'&&s[x+1][y+1]=='.'&&s[x][y+1]=='.')||(s[x-1][y]=='.'&&s[x-1][y-1]=='.'&&s[x][y-1]=='.')||(s[x+1][y]=='.'&&s[x+1][y-1]=='.'&&s[x][y-1]=='.') ||(s[x-1][y]=='.'&&s[x-1][y+1]=='.'&&s[x][y+1]=='.'))
            s[x][y]='.';
        else
            continue;
        q.push({x+1,y-1});
        q.push({x+1,y});
        q.push({x+1,y+1});
        q.push({x,y-1});
        q.push({x,y+1});
        q.push({x-1,y-1});
        q.push({x-1,y});
        q.push({x-1,y+1});
    }
    for(int i=1;i<=n;i++)
        printf("%s\n",s[i]+1);
}
525D BFS版本
考虑枚举断电,则问题转化成某个位置i,前面匹配的字符串数量和后面匹配的字符串数量相乘,ac自动机搞一搞即可。
#define N 400010
struct 
{
    ll t[N][26],val[N],fail[N],vis[N];
    ll cnt=0;
    void insert(char *a,ll n)
    {
        ll u=0;
        for(ll i=1;i<=n;i++)
        {
            ll &v=t[u][a[i]-'a'];
            if(!v)
            {
                cnt++;
                v=cnt;
            }
            u=v;
        }
        val[u]++;
    }
    void build()
    {
        queue<ll>q;
        for(int i=0;i<26;i++)
            if(t[0][i])
                q.push(t[0][i]);
        while(q.size())
        {
            ll u=q.front();
            q.pop();
            for(int i=0;i<26;i++)
            {
                ll &v=t[u][i];
                if(v)
                    fail[v]=t[fail[u]][i],val[v]+=val[fail[v]],q.push(v);
                else
                    v=t[fail[u]][i];
            }
        }
    }
    void Query(char *a,ll n,ll f[])
    {
        for(ll i=0;i<=cnt;i++)
            vis[i]=0;
        ll u=0;
        for(ll i=1;i<=n;i++)
        {
            u=t[u][a[i]-'a'];
            f[i]=val[u];
        }
    }
}ac,Rac;
ll f1[N],f2[N];
char a[N],b[N];
int main()
{
    // freopen("1.in","r",stdin);
    scanf("%s",a+1);
    ll la=strlen(a+1);
    for(ll n=read();n;n--)
    {
        scanf("%s",b+1);
        ll lb=strlen(b+1);
        ac.insert(b,lb);
        reverse(b+1,b+lb+1);
        Rac.insert(b,lb);
    }
    ac.build();
    Rac.build();
    ac.Query(a,la,f1);
    reverse(a+1,a+la+1);
    Rac.Query(a,la,f2);
    ll ans=0;
    for(ll i=1;i<=la;i++)
        ans+=f1[i]*f2[la-i];
    cout<<ans;
}
1202E
考虑手玩几个,大胆分类讨论,模拟类问题,用cnt维护点数,e维护边数
首先如果全都连通,输出0
如果存在一个独立的点,也就是fa[i]==i&&cnt[i]==1可以对它进行操作,输出1
如果存在一个并查集,并非团,则找一找度数最小的点,他一定不是割点,输出1和这个点
如果全部都是团,则看看团的数量
如果有两个团,则输出siz最小的团里的所有点
如果有多个团,则输出两个不同团里的点
int fa[4010],cnt[4010],n,du[4010],e[4010];
char s[4010][4010];
int get(int x)
{
    return fa[x]==x?x:fa[x]=get(fa[x]);
}
int print0()
{
    for(int i=1;i<=n;i++)
        if(get(i)!=get(1))
            return 0;
    cout<<"0\n";
    return 1;
}
int print1()
{
    for(int i=1;i<=n;i++)
    {
        if(fa[i]!=i)
            continue;
        if(cnt[i]==1)
        {
            cout<<1<<'\n';
            cout<<i<<'\n';
            return 1;
        }
        if((cnt[i]*(cnt[i]-1)/2!=e[i]))
        {
            int v=i;
            for(int x=1;x<=n;x++)
            {
                if(get(x)==i&&du[x]<du[v])
                    v=x;
            }
            cout<<"1\n"<<v<<'\n';
            return 1;
        }
    }
    return 0;
}
void work()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s[i]+1);
        fa[i]=i;
        cnt[i]=1;
        e[i]=0;
        du[i]=0;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            if(s[i][j]=='1')
            {
                du[i]++;
                du[j]++;
                if(get(i)==get(j)){
                    e[get(i)]++;
                    continue;
                }
                cnt[fa[i]]+=cnt[fa[j]];
                e[fa[i]]+=e[fa[j]]+1;
                fa[fa[j]]=fa[i];
            }
        }
    }
    if(print0())
        return ;
    if(print1())
        return ;
    int l=0,r=0;
    for(int i=1;i<=n;i++)
    {
        if(get(i)!=i)
            continue;
        if(l==0)
            l=i;
        else if(r==0)
            r=i;
        else
        {
            cout<<2<<'\n';
            cout<<l<<' '<<r<<'\n';
            return ;
        }
    }
    int v=-1;
    for(int i=1;i<=n;i++)
    {
        if(get(i)!=i)
            continue;
        if(v==-1||cnt[i]<cnt[v])
            v=i;
    }
    cout<<cnt[v]<<'\n';
    for(int i=1;i<=n;i++)
        if(get(i)==v)
            cout<<i<<' ';
    cout<<'\n';
}
int main()
{
    // freopen("1.in","r",stdin);
    // freopen("C.out","w",stdout);
    for(int t=read();t;t--)
        work();
1761E
首先使用莫比乌斯反演,把问题的询问gcd=i改成gcd=i的倍数的链的数量
为了解决这个问题,可以枚举i,把所有a[x]%i=0的点x看做白点,则白点的连通块内任选两点所形成的链都是gcd为i的倍数的链。使用dfs询问siz即可
考虑复杂度,每个数字的因子数量是166320和196560最多,有160个因子。每个点被dfs到的次数为因子数量,故总复杂度为O(160n)
const int N=200010;
int siz[N],a[N],n;
ll g[N],f[N],mu[N];
vector<int>pos[N],yin[N],e[N];
void dfs(int x,int v)
{
    siz[x]=1;
    for(auto y:e[x])
    {
        if(siz[y]||a[y]%v)
            continue;
        dfs(y,v);
        siz[x]+=siz[y];
    }
}
void Euler()
{
    int Prm[N],vis[N],Pcnt=0;
    memset(vis,0,sizeof(vis));
    mu[1]=1;
    for(int i=2;i<=N-5;++i){
        if(!vis[i]) Prm[++Pcnt]=i,mu[i]=-1;
        for(int j=1;j<=Pcnt&&i*Prm[j]<=N-5;++j){
            vis[i*Prm[j]]=1;
            if(i%Prm[j]==0) break;
            mu[i*Prm[j]]=-mu[i];
        }
    }
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int i=2;i<=200000;i++)
        for(int j=i;j<=200000;j+=i)
            yin[j].push_back(i);
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        for(auto v:yin[a[i]])
            pos[v].push_back(i);
    }
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        e[x].push_back(y);
        e[y].push_back(x);
    }
    g[1]=n*(n+1ll)/2;
    for(int i=2;i<=200000;i++)
    {
        for(auto x:pos[i])
        {
            if(siz[x])
                continue;
            dfs(x,i);
            g[i]+=siz[x]*(siz[x]+1ll)/2;
        }
        for(auto x:pos[i])
            siz[x]=0;
    }
    Euler();
    for(int i=1;i<=200000;++i)
        for(int j=i;j<=200000;j+=i)
            f[i]+=g[j]*mu[j/i];
    for(int i=1;i<=200000;i++)
        if(f[i])
            cout<<i<<' '<<f[i]<<'\n';
}
990G
如果-1并非an,则必须贿赂n号
而n号可以为我们最多解决任意n/2个对手
如果这还不够,我们需要从n/2到n里再挑一个人贿赂
找更小的则无法保证必赢
于是以此类推
int n,a[300000];
ll ans;
priority_queue<int,vector<int>,greater<int>>q;
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    for(int i=n;a[i]!=-1;i--)
    {
        q.push(a[i]);
        if((i&(i-1))==0)
        {
            ans=ans+q.top();
            q.pop();
        }
    }
    cout<<ans;
}
1260E
太强了,还能这样哈希
考虑直接拿着模式串和匹配串匹配复杂度无法接受
于是考虑枚举串长,枚举这个长度的所有哈希值,去和小串进行匹配。则复杂度变为n根号n
套一个unordered set,就做完了
unordered_set<ll>a[300010];
unordered_set<ll>lenlen;
int p[300010],f[300010],mod=1000000007;
char s[300010];
int main()
{
    // freopen("1.in","r",stdin);
    p[0]=1;
    for(int i=1;i<=300000;i++)
        p[i]=p[i-1]*233ll%mod;
    for(int t=read();t;t--)
    {
        int x=read();
        if(x==1)
        {
            scanf("%s",s);
            int len=strlen(s),res=0;
            for(int i=0;i<len;i++)
                res=((ll)res*233+s[i])%mod;
            a[len].insert(res);
            lenlen.insert(len);
        }
        else if(x==2)
        {
            scanf("%s",s);
            int len=strlen(s),res=0;
            for(int i=0;i<len;i++)
                res=((ll)res*233+s[i])%mod;
            a[len].erase(res);
        }
        else
        {
            scanf("%s",s+1);
            int n=strlen(s+1),ans=0;
            for(int i=1;i<=n;i++)
                f[i]=((ll)f[i-1]*233+s[i])%mod;
            for(auto len:lenlen)
                for(int i=len;i<=n;i++)
                    if(a[len].count( ((f[i]-1ll*f[i-len]*p[len])%mod+mod)%mod))
                        ans++;
            cout<<ans<<endl;
        }
    }
}
710F
很妙的构造
首先某个数字出现奇数次一定不行。
否则一定可以
考虑给每一行一对一对的连边
并且给每个数字进行一对一对的连边
然后黑白染色一下
int m,n[100010],cnt,x[200010],y[200010];
string ans[200010];
map<int,vector<int>>pos;
vector<int>e[200010];
void dfs(int now)
{
    for(auto nxt:e[now])
    {
        if(ans[x[nxt]][y[nxt]])continue;
        if(ans[x[now]][y[now]]=='L'){
            ans[x[nxt]][y[nxt]]='R';
            dfs(nxt);
        }
        else {
            ans[x[nxt]][y[nxt]]='L';
            dfs(nxt);
        }
    }
}
int main()
{
    // freopen("1.in","r",stdin);
    m=read();
    for(int i=1;i<=m;i++)
    {
        n[i]=read();
        // a[i].resize(n[i]);
        ans[i].resize(n[i]);
        for(int j=0;j<n[i];j++){
            int t=read();
            cnt++;
            x[cnt]=i;
            y[cnt]=j;
            pos[t].push_back(cnt);
        }
    }
    for(int i=1;i<cnt;i+=2)
    {
        e[i].push_back(i+1);
        e[i+1].push_back(i);      
    }
    for(auto o:pos)
    {

        if(o.second.size()&1)
        {
            cout<<"NO";
            return 0;
        }
        for(int j=0;j<o.second.size();j+=2)
        {
            e[o.second[j]].push_back(o.second[j+1]);
            e[o.second[j+1]].push_back(o.second[j]);
        }
    }
    for(int i=1;i<=cnt;i++)
    {
        if(ans[x[i]][y[i]])continue;
        ans[x[i]][y[i]]='R';
        dfs(i);
    }
    cout<<"YES\n";
    for(int i=1;i<=m;i++)
        cout<<ans[i]<<'\n';
}
1634E

 

考虑暴力修改
复杂度n*10000+30*n*logn
跑得飞快
int f[10010],n,m,a[100010],c[100010];
void add(int i,int v)
{
    while(i<=n)
    {
        c[i]+=v;
        i=i+(i&(-i));
    }
}
int ask(int i)
{
    int t=0;
    while(i)
    {
        t+=c[i];
        i=i-(i&(-i));
    }
    return t;
}
int check(int x)
{
    while(x)
    {
        if(x%10!=4&&x%10!=7)
            return 0;
        x=x/10;
    }
    return 1;
}
char s[10];
int main()
{
    // freopen("1.in","r",stdin);
    for(int i=1;i<=10000;i++)
        f[i]=check(i);
    n=read();m=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
        if(f[a[i]])
            add(i,1);
    }
    for(;m;m--)
    {
        scanf("%s",s);
        if(s[0]=='c')
        {
            int l=read();
            cout<<ask(read())-ask(l-1)<<'\n';
        }
        else
        {
            int l=read(),r=read(),v=read();
            for(int i=l;i<=r;i++)
            {
                if(f[a[i]])
                    add(i,-1);
                a[i]+=v;
                if(f[a[i]])
                    add(i,1);
            }
        }
    }
}
121E

 

如果边abw是合法的
则存在对于xi yi vi
使得dist(xi,a)+w+dist(b,yi)<=vi
也就是-vi+dist(b,yi) <=-w-dist(xi,a)
我们对于固定的xi
预处理出
d[yi]=-vi+dist(x,b)
则对于边abw
判断d[yi]是否<=-w-dist(xi,a)即可
预处理d[yi]复杂度n^3
有了d[yi],扫一遍所有的边,复杂度+n^3
int n,m,q;
ll dist[610][610],d[610];
vector<pair<int,int>>e[610];
struct edge
{
    ll x,y,v,ans;
}E[360000];
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    memset(dist,0x3f,sizeof(dist));
    for(int i=1;i<=m;i++)
    {
        ll x=read(),y=read(),v=read();
        E[i]={x,y,v};
        dist[y][x]=dist[x][y]=min(dist[x][y],v);
    }
    q=read();
    for(int i=1;i<=q;i++)
    {
        int x=read(),y=read(),v=read();
        e[x].push_back({y,v});
        e[y].push_back({x,v});
    }
    for(int x=1;x<=n;x++)
        dist[x][x]=0;
    for(int k=1;k<=n;k++)
        for(int x=1;x<=n;x++)
            for(int y=1;y<=n;y++)
                dist[x][y]=min(dist[x][y],dist[x][k]+dist[k][y]);
    for(int x=1;x<=n;x++)
    {
        memset(d,0x3f,sizeof(d));
        for(auto y:e[x])
            for(int b=1;b<=n;b++)
                d[b]=min(d[b],-y.second+dist[y.first][b]);
        for(int i=1;i<=m;i++)
            if(d[E[i].y]<=-E[i].v-dist[x][E[i].x]||d[E[i].x]<=-E[i].v-dist[x][E[i].y])
                E[i].ans=1;
    }
    int ans=0;
    for(int i=1;i<=m;i++)
        ans=ans+E[i].ans;
    cout<<ans;
}
1482F

 

posted @ 2023-04-12 11:16  zzuqy  阅读(144)  评论(0编辑  收藏  举报