30道codeforces2100分(施工中)

Problemset - Codeforces

 

考虑这些被释放的,值一定相同,并且等于区间gcd
于是用st表询问区间gcd,map套二分实现区间里某个数字出现次数

int n,a[100010];
int f[100010][20],lgn[100010];
map<int,vector<int>>pos;
int ask(int l,int r)
{
    int s=lgn[r-l+1];
    return __gcd(f[l][s],f[r-(1<<s)+1][s]);
}
int main() 
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++){
        f[i][0]=a[i]=read();
        pos[a[i]].push_back(i);
    }
    lgn[1]=0;
    for(int i=2;i<=n;i++)
        lgn[i]=lgn[i/2]+1;
    int logn=lgn[n];
    for(int j=1;j<=logn;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            f[i][j]=__gcd(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    for(int m=read();m;m--)
    {
        int l=read(),r=read();
        int gcd=ask(l,r);
        if(lower_bound(pos[gcd].begin(),pos[gcd].end(),l)==pos[gcd].end())
            cout<<r-l+1<<"\n";
        else
            cout<<r-l+1-(upper_bound(pos[gcd].begin(),pos[gcd].end(),r)-lower_bound(pos[gcd].begin(),pos[gcd].end(),l))<<'\n';
    }
}
474F
注意到确定一个起点后,这棵树的答案就是固定的了,是Σd[i],其中d[x]表示到起点的路径上的点的数量
用换根dp维护之
具体地,第一遍dfs得到初始的d数组和f数组,f[i]表示令1为根,以i为根的子树的Σd[x]
第二遍dfs维护上面的点距离x的距离之和与上面的点的数量,往下走的时候修改一下即可

int n;
vector<int>e[200010];
ll f[200010],ans,d[200010],cnt[200010];
void dfs(int x)
{
    f[x]=d[x];
    cnt[x]=1;
    for(auto y:e[x]){
        if(d[y])
            continue;
        d[y]=d[x]+1;
        dfs(y);
        f[x]=f[x]+f[y];
        cnt[x]+=cnt[y];
    }
}
void dfs1(int x,ll sum,int num)
{
    ans=max(ans,f[x]-(d[x]-1)*cnt[x]+sum);
    for(auto y:e[x])
    {
        if(d[y]<d[x])
            continue;
        int numm=cnt[x]-cnt[y];//子树里除了y的子树还剩几个点
        dfs1(y,sum+num+f[x]-f[y]-(d[x]-2)*numm,num+numm);
    }
}
int main() 
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        e[x].push_back(y);
        e[y].push_back(x);
    }
    d[1]=1;
    dfs(1);
    dfs1(1,0,0);
    cout<<ans;
}
1187E
考虑二分答案
对于mid,考虑枚举ai,则需要检查[k*a[i]+x,k*a[i]+a[i]-1]这个区间里有没有数字
复杂度nlog^2
不二分答案也行,考虑枚举ai,查找[k*a[i],(k+1)*a[i])区间里最后一个出现的数字,更新答案,复杂度仍然是nlog^2
int n,a[200010],c[1000010];
int check(int x)
{
    for(int i=1;i<=n;i++)
        for(int r=min(1000000,a[i]*2-1),l=a[i]+x;l<=r;l+=a[i],r=min(1000000,r+a[i]))
            if(c[r]-c[l-1])
                return 1;
    return 0;
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    sort(a+1,a+1+n);
    n=unique(a+1,a+1+n)-a-1;
    for(int i=1;i<=n;i++)
        c[a[i]]=1;
    for(int i=1;i<=1000000;i++)
        c[i]+=c[i-1];
    int l=1,r=1000000,mid;
    while(l+1<r)
    {
        mid=(l+r)/2;
        if(check(mid))
            l=mid;
        else
            r=mid;
    }
    if(check(r))
        cout<<r;
    else if(check(l))
        cout<<l;
    else
        cout<<0;
}
484B
点分治板子
考虑1 2 3 4 5 6 7这个链应该怎么做
可以发现是4最高,26次之,1357最低
于是点分治的过程中给分治的那个点赋值即可

int vis[100010],siz[100010],wt[100010];
int n,Root,Tsiz,now='A'-1;
char ans[100010];
vector<int>e[100010];
void getroot(int x,int f)
{
    siz[x]=1;
    wt[x]=0;
    for(auto y:e[x])
    {
        if(y==f||vis[y])
            continue;
        getroot(y,x);
        siz[x]+=siz[y];
        wt[x]=max(wt[x],siz[y]);
    }
    wt[x]=max(wt[x],Tsiz-siz[x]);
    if(wt[Root]>wt[x])
        Root=x;
}
void DFS(int x)
{
    now++;
    ans[x]=now;
    vis[x]=1;
    for(auto y:e[x])
    {
        if(vis[y])
            continue;
        Root=0;
        Tsiz=siz[y];
        getroot(y,0);
        DFS(Root);
    }
    now--;
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        e[x].push_back(y);
        e[y].push_back(x);
    }
    wt[Root=0]=0x3f3f3f3f;
    Tsiz=n;
    getroot(1,0);
    ans[Root]=now;
    DFS(Root);
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<' ';
}
321C

 

先跑一个最小生成树
对于边xyw
如果原本就被取用了,当然输出最小生成树的权值和
否则,考虑在最小生成树上跑lca来求xy路径上的最大边权,用w来替代之,输出新的边权和
int n,m,fa[200010],f[200010][20],maxx[200010][20],d[200010];
ll sum;
int get(int x)
{
    return fa[x]==x?x:fa[x]=get(fa[x]);
}
struct edge
{
    int i,x,y,w,flag;
}o[200010];
bool w_(edge a,edge b)
{
    return a.w<b.w;
}
bool i_(edge a,edge b)
{
    return a.i<b.i;
}
vector<pair<int,int>>e[200010];
void dfs(int x)
{
    for(int i=0;i<e[x].size();i++)
    {
        int y=e[x][i].first,w=e[x][i].second;
        if(d[y])
            continue;
        d[y]=d[x]+1;
        f[y][0]=x;
        maxx[y][0]=w;
        dfs(y);
    }
}
int lca(int x,int y)
{
    int t=0;
    if(d[x]<d[y])
        swap(x,y);
    for(int i=19;i>=0;i--)
        if(d[f[x][i]]>=d[y])
        {
            t=max(t,maxx[x][i]);
            x=f[x][i];
        }
    if(x==y)
        return t;
    for(int i=19;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            t=max(t,maxx[x][i]);
            t=max(t,maxx[y][i]);
            x=f[x][i];
            y=f[y][i];
        }
    }
    return max(max(maxx[x][0],maxx[y][0]),t);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        o[i].i=i;
        o[i].x=read();
        o[i].y=read();
        o[i].w=read();
    }
    sort(o+1,o+1+m,w_);
    for(int i=1;i<=m;i++)
    {
        if(get(o[i].x)==get(o[i].y))
            continue;
        e[o[i].x].push_back({o[i].y,o[i].w});
        e[o[i].y].push_back({o[i].x,o[i].w});
        sum=sum+o[i].w;
        o[i].flag=1;
        fa[fa[o[i].x]]=fa[o[i].y];
    }
    sort(o+1,o+1+m,i_);
    d[1]=1;
    dfs(1);
    for(int i=1;i<20;i++)
        for(int x=1;x<=n;x++)
        {
            f[x][i]=f[f[x][i-1]][i-1];
            maxx[x][i]=max(maxx[x][i-1],maxx[f[x][i-1]][i-1]);
        }
    for(int i=1;i<=m;i++)
        if(o[i].flag)
            printf("%lld\n",sum);
        else
            printf("%lld\n",sum-lca(o[i].x,o[i].y)+o[i].w);

}
609E

 

考虑二分答案x
把如果ai大于等于x,看做1,如果小于x,看做0,赋值给bi
于是问题转变成有没有长大于等于k的区间,满足区间和大于0
于是前缀和,问题转变成对于前缀和bi,在它k个之后,有没有大于他的
int n,k,a[200010],b[200010];
multiset<int>o;
int check(int x)
{
    o.clear();
    for(int i=1;i<=n;i++)
    {
        if(a[i]<x)
            b[i]=b[i-1]-1;
        else
            b[i]=b[i-1]+1;
        if(i>=k)
            o.insert(b[i]);
    }
    for(int l=0,r=k;r<=n;l++,r++)
    {
        if(*o.rbegin()>b[l])
            return 1;
        o.erase(o.find(b[r]));
    }
    return 0;
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();k=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    int l=1,r=200000,mid;
    while(l+1<r)
    {
        mid=(l+r)/2;
        if(check(mid))
            l=mid;
        else
            r=mid;
    }
    if(check(r))
        cout<<r;
    else
        cout<<l;
}
1486D

 

着急吃饭导致wa了两发
实现一个倍增求lca,和一个在树上往上跳若干次,返回跳到哪里的函数
于是分类讨论
如果xy距离是奇数输出0
如果x=y输出n
如果d[x]=d[y],则把x跳到lca下面,y跳到lca下面,输出n-cnt[x]-cnt[y]
否则,把z跳到dis/2位置,x跳到z下面,输出cnt[z]-cnt[x]
int n,d[100010],f[100010][20],cnt[100010];
vector<int>e[100010];
int lca(int x,int y)
{
    if(d[x]<d[y])
        swap(x,y);
    for(int i=19;i>=0;i--)
        if(d[f[x][i]]>=d[y])
            x=f[x][i];
    if(x==y)
        return x;
    for(int i=19;i>=0;i--)
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    return f[x][0];
}
void dfs(int x)
{
    cnt[x]=1;
    for(auto y:e[x])
    {
        if(d[y])
            continue;
        d[y]=d[x]+1;
        f[y][0]=x;
        dfs(y);
        cnt[x]+=cnt[y];
    }
}
int ask(int x,int d)
{
    for(int i=19;i>=0;i--)
    {
        if(d>=(1<<i)){
            x=f[x][i];
            d-=(1<<i);
        }
    }
    return x;
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        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++)
            f[x][i]=f[f[x][i-1]][i-1];
    for(int m=read();m;m--)
    {
        int x=read(),y=read();
        if(x==y)
        {
            cout<<n<<'\n';
            continue;
        }
        if(d[x]<d[y])
            swap(x,y);
        int z=lca(x,y),dis=d[x]+d[y]-2*d[z];
        if(dis&1)
            cout<<"0\n";
        else if(d[x]==d[y])
        {
            z=ask(x,dis/2);
            x=ask(x,dis/2-1);
            y=ask(y,dis/2-1);
            cout<<n-cnt[x]-cnt[y]<<'\n';
        }
        else
        {
            z=ask(x,dis/2);
            x=ask(x,dis/2-1);
            cout<<cnt[z]-cnt[x]<<'\n';
        }
    }
}
519E

 

首先注意到,x的兄弟们一定和他深度相同
那这个p什么用处呢,他规定了询问都有p次祖先的兄弟们
于是跑一跑dfs序,预处理倍增,令y是x的p次祖先
被询问的兄弟就是y的子树里,和x同一深度的点的数量。也就是dfs序处于[l[y],r[y]]之间的深度是d[x]的点的数量
用vector维护之
int tot,l[100010],r[100010],f[100010][20],dfn[100010],d[100010];
vector<int>o[100010],e[100010];
void dfs(int x)
{
    tot++;
    l[x]=r[x]=dfn[x]=tot;
    o[d[x]].push_back(dfn[x]);
    for(auto y:e[x])
    {
        d[y]=d[x]+1;
        f[y][0]=x;
        dfs(y);
        r[x]=r[y];
    }
}
int ask(int x,int t)
{
    for(int i=19;i>=0;i--)
        if(t>=(1<<i)){
            t-=(1<<i);
            x=f[x][i];
        }
    return x;
}
int main()
{
    // freopen("1.in","r",stdin);
    int n=read();
    for(int i=1;i<=n;i++)
        e[read()].push_back(i);
    for(auto y:e[0])
    {
        d[y]=1;
        dfs(y);
    }
    for(int i=1;i<=19;i++)
        for(int x=1;x<=n;x++)
            f[x][i]=f[f[x][i-1]][i-1];

    for(int m=read();m;m--)
    {
        int x=read(),p=read();
        int y=ask(x,p);
        if(y==0)
            cout<<0<<' ';
        else
            cout<<upper_bound(o[d[x]].begin(),o[d[x]].end(),r[y])-lower_bound(o[d[x]].begin(),o[d[x]].end(),l[y])-1<<' ';
    }
}
208E

 

是不是走错地方了,2100显然比2000简单的多
考虑dp,对于每一行,01背包,f[i][j]表示选了i个数字,数字之和%k=j的最大数字之和
每行的求出来之后,再更新g[i][j]表示前i行数字之和%k=j的最大数字之和,这里是分组背包
n,m只有70,大力n^4做即可
int n,m,k,a[71],f[71][71],g[71][71];
void work()
{
    memset(f,0xef,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=m;i++)
    {
        a[i]=read();
        for(int cnt=i-1;cnt>=0;cnt--)
            for(int j=0;j<k;j++)
                f[cnt+1][(j+a[i])%k]=max(f[cnt+1][(j+a[i])%k],f[cnt][j]+a[i]);
    }
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();k=read();
    memset(g,0xef,sizeof(g));
    g[0][0]=0;
    for(int i=1;i<=n;i++)
    {
        for(int x=0;x<k;x++)
            g[i][x]=max(g[i][x],g[i-1][x]);
        work();
        for(int cnt=1;cnt<=m/2;cnt++)
            for(int x=0;x<k;x++)
            {
                g[i][x]=max(g[i][x],g[i-1][x]);
                for(int y=0;y<k;y++)
                    g[i][(x+y)%k]=max(g[i][(x+y)%k],f[cnt][y]+g[i-1][x]);
            }
    }
    cout<<g[n][0];
}
1433F

 

考虑从每个点bfs,每次从set里面挑点,如果和x有连边就是同一并查集,否则continue
复杂度只有(m+n)log

int n,m,vis[200010];
set<int>e[200010],o;
queue<int>q;
priority_queue<int,vector<int>,greater<int>>ans;
void bfs(int x)
{
    q.push(x);
    o.erase(x);
    int cnt=0;
    while(q.size())
    {
        x=q.front();
        q.pop();
        if(vis[x])continue;
        vis[x]=1;
        cnt++;
        for(auto i=o.begin();i!=o.end();)
        {
            int y=*i;
            i++;
            if(e[x].find(y)==e[x].end())
            {
                q.push(y);
                o.erase(y);
            }
        }
    }
    ans.push(cnt);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
        o.insert(i);
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        e[x].insert(y);
        e[y].insert(x);
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i])
            continue;
        bfs(i);
    }
    cout<<ans.size()<<'\n';
    while(ans.size())
    {
        cout<<ans.top()<<' ';
        ans.pop();
    }
}
920E

 

考虑区间dp
f[l][r]表示区间lr最少变成几个数字
f[l][r]=min(f[l][m]+f[m+1][r])
如果l,m和m+1,r都能变成一个数字,我们用v数组记录他变成了哪个数字,如果v也相同,flr就可以=1
int f[510][510];
int n,a[510],v[510][510];
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        v[i][i]=a[i];
        f[i][i]=1;
    }
    for(int len=2;len<=n;len++)
        for(int l=1,r=len;r<=n;l++,r++)
            for(int m=l;m<r;m++)
                if(f[l][m]==1&&f[m+1][r]==1&&v[l][m]==v[m+1][r])
                {
                    f[l][r]=1,v[l][r]=v[l][m]+1;
                    break;
                }
                else
                    f[l][r]=min(f[l][r],f[l][m]+f[m+1][r]);
    cout<<f[1][n];
}
1312E

 

如果已知gcd(a,b)和lcm(a,b),令t表示lcm(a,b)/gcd(a,b)的质因子种类数
则a和b的不同方案数是2的t次方
考虑问的是c*lcm-d*gcd=x,不妨令xcd除以gcd(x,c,d)
然后令a=agcd,b=bgcd,则lcm=abgcd
cab*gcd=d*gcd=x
这要求x整除gcd,可以枚举,复杂度t根号x
cab=x/gcd+d,这要求x/gcd+d整除c,可以模一下判断一下
ab=(x/gcd+d)/c
问题转变成互质的ab,已知乘积
只需要求出乘积的质因子种类数,求2的他的幂次就好了
那么问题转变成o1询问某个数字的质因子种类数
考虑用埃氏筛预处理一下,跑0.5s即可
int f[20000010];
ll ans,g[110];
ll c,d,x;
void add(ll gcd)
{
    ll t=x/gcd+d;
    if(t%c)
        return ;
    t=t/c;
    ans=ans+g[f[t]];
}
void work()
{
    ans=0;
    c=read(),d=read(),x=read();
    ll t=__gcd(__gcd(c,d),x);
    c=c/t;d=d/t;x=x/t;
    for(int gcd=1;gcd*gcd<=x;gcd++)
    {
        if(x%gcd)
            continue;
        add(gcd);
        if(x/gcd!=gcd)
            add(x/gcd);
    }
    cout<<ans<<'\n';
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int i=2;i<=20000000;i++)
    {
        if(f[i])continue;
        for(int j=i;j<=20000000;j+=i)
            f[j]++;
    }
    g[0]=1;
    for(int i=1;i<=100;i++)
        g[i]=g[i-1]*2;
    for(int t=read();t;t--)
        work();
}
1499D

 

手玩一下n=5和n=7,可以发现一定是选2个相邻的,剩下的隔一个选一个
于是枚举相邻的是哪个,隔一个选一个可以用前缀和o1求出来
int n,a[200010];
ll ans,c[200010];
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    c[1]=a[1];
    for(int i=2;i<=n;i++)
        c[i]=c[i-2]+a[i];
    ans=c[n];
    for(int i=1;i<n;i++)
        ans=max(ans,c[i]+c[n-i%2]-c[i-1]);
    cout<<ans;
}
1372D

 

构造题
考虑斜着第一列,第四列,第七列。。。
和第二列,第五列,第八列。。。
和第三列,第六列,第九列。。。
在这三列里选,取min,总有至少一个小于等于sum/3
于是看看谁最小,用它即可
char s[1010][1010];
int n,dx[]={0,0,1,-1},dy[]={1,-1,0,0},cnt[10];
int ask(int x,int y)
{
    if(x>n||y>n)
        return 0;
    for(int i=0;i<4;i++)
    {
        if(min(x+2*dx[i],y+2*dy[i])<1||max(x+2*dx[i],y+2*dy[i])>n)
            continue;
        if(s[x][y]=='X'&&s[x+dx[i]][y+dy[i]]=='X'&&s[x+2*dx[i]][y+2*dy[i]]=='X')
            return 1;
    }
    for(int i=0;i<4;i++)
        if(s[x][y]=='X'&&s[x+dx[i]][y+dy[i]]=='X'&&s[x-dx[i]][y-dy[i]]=='X')
            return 1;
    return 0;
}
void work()
{
    n=read();
    for(int i=1;i<=n;i++)
        scanf("%s",s[i]+1);
    for(int j=1;j<=3;j++)
    {  
        cnt[j]=0;
        for(int i=j;i<=3*n;i+=3)
            for(int x=1,y=i;y;x++,y--)
                if(ask(x,y))
                    cnt[j]++;
    }
    int j;
    if(min(cnt[1],min(cnt[2],cnt[3]))==cnt[1])
        j=1;
    else if(min(cnt[1],min(cnt[2],cnt[3]))==cnt[2])
        j=2;
    else
        j=3;

    for(int i=j;i<=3*n;i+=3)
        for(int x=1,y=i;y;x++,y--)
            if(ask(x,y))
                s[x][y]='O';
    for(int i=1;i<=n;i++)
        printf("%s\n",s[i]+1);
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1450C1

 

数学,计算几何题
最多100边形,考虑枚举一下,判断夹角是否能够整除360/n

struct node
{
    double x,y;
}A,B,C,D;
double v1,v2,v3,r,v[5];
double ask(node a,node b,node c)
{
    a.x-=c.x;
    a.y-=c.y;
    b.x-=c.x;
    b.y-=c.y;
    return acos((a.x*b.x+a.y*b.y)/r/r);
}
void work(int n)
{
    double t=2*acos(-1)/n;

    for(int i=1;i<=3;i++)
        if( fabs(v[i]-round(v[i]/t)*t)>1e-5)
            return ;
    printf("%8lf",r*r*sin(t)*n/2);
    exit(0);
}
int main()
{
    // freopen("1.in","r",stdin);
    cin>>A.x>>A.y>>B.x>>B.y>>C.x>>C.y;
    D.x=((B.y-A.y)*(C.y*C.y-A.y*A.y+C.x*C.x-A.x*A.x)-(C.y-A.y)*(B.y*B.y-A.y*A.y+B.x*B.x-A.x*A.x))/(2.0*((C.x-A.x)*(B.y-A.y)-(B.x-A.x)*(C.y-A.y)));
    D.y=((B.x-A.x)*(C.x*C.x-A.x*A.x+C.y*C.y-A.y*A.y)-(C.x-A.x)*(B.x*B.x-A.x*A.x+B.y*B.y-A.y*A.y))/(2.0*((C.y-A.y)*(B.x-A.x)-(B.y-A.y)*(C.x-A.x)));
    r=(A.x-D.x)*(A.x-D.x)+(A.y-D.y)*(A.y-D.y);
    r=sqrt(r);
    v[1]=ask(A,B,D);
    v[2]=ask(A,C,D);
    v[3]=ask(B,C,D);
    for(int n=3;n<=100;n++)
        work(n);
}
1C
树上dfs序相关
给v注水可以使用dfs序,线段树区间修改,再把区间里的set里的点删掉,替换为v的父亲
清空节点v,我们可以使用set插入
判断v是否装满水,我们先看看set里面有没有v的儿子,如果有,显然输出0。否则输出线段树单点询问的值即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int read()
{
    int x;scanf("%d",&x);return x;
}
int cnt,dfn[500010],l[500010],r[500010];
int n,lazy[2000010],fa[500010];
vector<int>e[500010];
set<int>o;
void dfs(int x)
{
    cnt++;
    dfn[x]=l[x]=r[x]=cnt;
    for(auto y:e[x])
    {
        if(dfn[y])
            continue;
        fa[y]=x;
        dfs(y);
        r[x]=r[y];
    }
}
void build(int x,int l,int r)
{
    lazy[x]=-1;//全是空的
    if(l==r)
        return ;
    int mid=(l+r)/2;
    build(x*2,l,mid);
    build(x*2+1,mid+1,r);
}
void add(int x,int l,int r,int tl,int tr)
{
    if(lazy[x]==1)
        return;
    if(tl<=l&&r<=tr)
    {
        lazy[x]=1;
        return ;
    }
    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);
    if(lazy[x*2]==1&&lazy[x*2+1]==1)
        lazy[x]=1;
    else
        lazy[x]=-1;
}
int ask(int x,int l,int r,int d)
{
    if(lazy[x]==1)
        return 1;
    if(l==r)
        return 0;
    int mid=(l+r)/2;
    if(d<=mid)
        return ask(x*2,l,mid,d);
    else
        return ask(x*2+1,mid+1,r,d);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs(1);
    build(1,1,n);
    for(int q=read();q;q--)
    {
        int t=read();
        if(t==1)
        {
            int x=read();
            add(1,1,n,l[x],r[x]);
            while(1)
            {
                auto t=o.lower_bound(l[x]);
                if(t==o.end()||*t>r[x])
                    break;
                o.erase(t);
                if(x==1)
                    continue;
                o.insert(dfn[fa[x]]);
            }
        }
        else if(t==2)
            o.insert(dfn[read()]);
        else 
        {
            int x=read();
            auto t=o.lower_bound(l[x]);
            if(t!=o.end()&&*t<=r[x])
            {
                cout<<"0\n";
                continue;
            }
            cout<<ask(1,1,n,dfn[x])<<'\n';
        }
    }
}
343D
使用直径和并查集的知识解决本题,在并查集合并的途中更新直径。
因为两个联通块相连,为了使得新连通块直径最长,需要连接两个直径的中点,分类讨论一下,发现新的直径为t=max((len[x]+1)/2+(len[y]+1)/2+1,max(len[x],len[y]))

int fa[300010],t,maxx,len[300010];
int n,m,q;
vector<int>o,e[300010];
int get(int x)
{
    return fa[x]==x?x:fa[x]=get(fa[x]);
}
void dfs(int x,int beg,int d)
{
    if(d>maxx){
        t=x;
        maxx=d;
    }
    o.push_back(x);
    fa[x]=beg;
    for(auto y:e[x])
    {
        if(fa[y])
            continue;
        dfs(y,beg,d+1);
    }
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();q=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        e[x].push_back(y);
        e[y].push_back(x);
    }
    for(int i=1;i<=n;i++)
    {
        if(fa[i])
            continue;
        maxx=0;
        t=i;;
        o.clear();
        dfs(i,i,0);
        for(auto x:o)
            fa[x]=0;
        o.clear();
        dfs(t,i,0);
        len[i]=maxx;
    }
    for(;q;q--)
    {
        if(read()==1)
            cout<<len[get(read())]<<'\n';
        else
        {
            int x=get(read());
            int y=get(read());
            if(x==y)
                continue;
            int t=max((len[x]+1)/2+(len[y]+1)/2+1,max(len[x],len[y]));
            fa[x]=y;
            len[y]=t;
        }
    }
}
455C
树上多源bfs
既然原图是合法的,把每个点的管辖范围的交界处的边删掉即可。
int n,k,d,v[300010];
queue<int>q;
map<int,int>o[300010];
vector<int>e[300010],ans;
int main()
{
    // freopen("1.in","r",stdin);

    n=read();k=read();d=read();
    for(int i=1;i<=k;i++)
    {
        int x=read();
        q.push(x);
        v[x]=x;
    }
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        o[x][y]=i;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    while(q.size())
    {
        int x=q.front();q.pop();
        for(auto y:e[x])
        {
            if(v[y])
                continue;
            v[y]=v[x];
            q.push(y);
        }
    }
    for(int x=1;x<=n;x++)
        for(auto y:e[x])
            if(v[x]!=v[y]&&o[x][y])
                ans.push_back(o[x][y]);
    cout<<ans.size()<<'\n';
    for(auto v:ans)
        cout<<v<<' ';
}
796D

 

跑一个dfs序,用一个long long来存储一个区间里每个颜色在区间里是否出现过,则区间修改使用线段树修改一下,区间询问用线段树区间询问一下。

ll a[1600010],lazy[1600010],c[400010];
int l[400010],r[400010],cnt;
int n,m;
vector<int>e[400010];
void pushdown(int x)
{
    lazy[x*2]=lazy[x*2+1]=lazy[x];
    a[x*2]=a[x*2+1]=lazy[x];
    lazy[x]=0;
}
int count(ll x)
{
    int sum=0;
    while(x)
    {
        sum++;
        x=x-(x&(-x));
    }
    return sum;
}
void add(int x,int l,int r,int d,ll v)
{
    a[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);
}
void dfs(int x)
{
    l[x]=r[x]=++cnt;
    add(1,1,n,cnt,c[x]);
    for(auto y:e[x])
    {
        if(l[y])
            continue;
        dfs(y);
    }
    r[x]=cnt;
}
void add(int x,int l,int r,int tl,int tr,ll v)
{
    if(tl<=l&&r<=tr)
    {
        lazy[x]=a[x]=v;
        return ;
    }
    if(lazy[x])
        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);
    a[x]=a[x*2]|a[x*2+1];
}
ll ask(int x,int l,int r,int tl,int tr)
{
    if(tl<=l&&r<=tr)
        return a[x];
    if(lazy[x])
        pushdown(x);
    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);
    n=read();m=read();
    for(int i=1;i<=n;i++)
        c[i]=(1ll<<(read()));
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read();
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs(1);
    for(;m;m--)
    {
        if(read()&1)
        {
            int x=read();
            add(1,1,n,l[x],r[x],1ll<<read());
        }
        else
        {
            int x=read();
            cout<<count(ask(1,1,n,l[x],r[x]))<<'\n';
        }
    }
}
620E

 

有向图连通性,写一个tarjan类似物。
如果没有环,显然是一种颜色。否则把回边赋值为2号颜色,出边赋值为1号颜色
int col[5010],n,m,cy,ans[5010];
vector<pair<int,int>>e[5010];
void dfs(int x)
{
    col[x]=1;
    for(auto it:e[x])
    {
        int y=it.first,i=it.second;
        if(!col[y])
        {
            dfs(y);
            ans[i]=1;
        }
        else if(col[y]==2)
            ans[i]=1;
        else
        {
            ans[i]=2;
            cy=1;
        }
    }
    col[x]=2;
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        e[x].push_back({y,i});
    }
    for(int i=1;i<=n;i++)
        if(col[i]==0)
            dfs(i);
    cout<<cy+1<<'\n';
    for(int i=1;i<=m;i++)
        cout<<ans[i]<<' ';
}
1217D

 

数学,stl
预处理一个vi表示i的最大质因子,则对修改的数字x进行质因子分解,复杂度是log的
考虑每次单点修改,对着位置i狠狠修改,顺便更新全局gcd即可。具体地,如果某个质因子的次数够了n次,则gcd乘上这个质因子,复杂度qlog^2

map<int,int>o[200010];
map<int,int>cnt[200010];
int n,q,a[200010],v[200010];
ll ans=1,mod=1e9+7;
int main()
{
    // freopen("1.in","r",stdin);
    for(int i=2;i<=200000;i++)
    {
        if(v[i])continue;
        for(int j=i;j<=200000;j+=i)
            v[j]=i;
    }
    n=read();q=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        while(a[i]!=1)
        {

            o[i][v[a[i]]]++;
            cnt[v[a[i]]][o[i][v[a[i]]]]++;
            if(cnt[v[a[i]]][o[i][v[a[i]]]]==n)
                ans=ans*v[a[i]]%mod;
            a[i]=a[i]/v[a[i]];
        }
    }
    for(;q;q--)
    {
        int i=read();
        a[i]=read();
        while(a[i]!=1)
        {

            o[i][v[a[i]]]++;
            cnt[v[a[i]]][o[i][v[a[i]]]]++;
            if(cnt[v[a[i]]][o[i][v[a[i]]]]==n)
                ans=ans*v[a[i]]%mod;
            a[i]=a[i]/v[a[i]];
        }
        cout<<ans<<'\n';
    }
}
1493D

 

注意到n只有200,大胆dp
用f[cnt][j]表示有cnt个数字,j个5,最多能拿到几个2。
输出max(min(i,f[k][i]))

int n,k,f[210][4000],sum,ans;
int main()
{
    // freopen("1.in","r",stdin);
    n=read();k=read();
    memset(f,0xef,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=n;i++)
    {
        ll a=read();
        int n2=0,n5=0;
        while(a%2==0)
            n2++,a/=2;
        while(a%5==0)
            n5++,a/=5;
        sum+=n5;
        for(int cnt=k;cnt>=1;cnt--)
            for(int j=sum;j>=n5;j--)
                f[cnt][j]=max(f[cnt][j],f[cnt-1][j-n5]+n2);
    }
    for(int i=0;i<=sum;i++)
        ans=max(ans,min(i,f[k][i]));
    cout<<ans;
}
837D

 

把描述st不相等的数组算一算
        if(s[i]==t[i])
            a[i]=a[i-1];
        else if(s[i]=='1')
            a[i]=a[i-1]+1;
        else
            a[i]=a[i-1]-1;
则答案为a数组的max(a[l]-a[r]),维护一下前缀最大最小值并更新答案即可。
int n,a[1000010];
char s[1000010],t[1000010];
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    scanf("%s",s+1);
    scanf("%s",t+1);
    for(int i=1;i<=n;i++)
    {
        if(s[i]==t[i])
            a[i]=a[i-1];
        else if(s[i]=='1')
            a[i]=a[i-1]+1;
        else
            a[i]=a[i-1]-1;
    }
    if(a[n])
        cout<<"-1";
    else
    {
        int ans=0,minn=0,maxx=0;
        for(int i=1;i<=n;i++)
        {
            minn=min(minn,a[i]);
            maxx=max(maxx,a[i]);
            ans=max(ans,a[i]-minn);
            ans=max(ans,maxx-a[i]);
        }
        cout<<ans;
    }
}
1370E

 

单点增加a[x]增加y
询问全体%y=x的下标i的ai之和
单点加直接加到数组身上是o1的
想到如果y很大的话我暴力就可以了,暴力枚举只需要n/y。这样如果根号分治,只需要解决y很小的情况
如果y很小的话,我可以维护sum[x][y]表示%y=x的下标i的ai之和,这样给a[x]+v等价于给sum[i][x%i]+v
根号分治之

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int read()
{
    int x;scanf("%d",&x);return x;
}
int n;
ll sum[710][710],a[500010];
int main()
{
    // freopen("1.in","r",stdin);
    
    for(int q=read();q;q--)
    {
        if(read()&1)
        {
            int x=read(),v=read();
            for(int i=1;i<=700;i++)
                sum[i][x%i]+=v;
            a[x]+=v;
        }
        else
        {
            int x=read(),y=read();
            
            ll ans=0;
            if(x<=700)
                ans=sum[x][y];
            else
            {
                for(int i=y;i<=500000;i+=x)
                    ans=ans+a[i];
            }
            cout<<ans<<'\n';
        }
    }
}
1207F

 

充电的花费是ai=0的下标最大的0
一开始不得不去砍1号,因为不把1号砍掉没法充电就死了。接下来一段时间内都需要拿着最大的b1去充电,如果先去砍n号要充很多次,如果先去砍2号虽然充的次数不多,但是不利于后续啊

注意到bn=0,如果砍到了n号,前面的所有没砍的都可以无花费地砍完。这样每棵树要么不砍,要砍就要砍到底。综上一定是从前往后砍,砍一部分,每个都砍到底。

考虑动态规划!


设f[i]表示把i号全部砍掉的花费,f[i]=min(f[j]+a[i]*b[j])表示上一次全部砍掉的下标是j,j+1~i-1全部忽略掉,来砍i的花费是a[i]+b[j]。



斜率优化之

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int read()
{
    int x;scanf("%d",&x);return x;
}
int n;
ll f[100010],b[100010],a[100010];
int q[100010],head,tail;
double calc(int x,int y)
{
    return 1.0*(f[y]-f[x])/(b[x]-b[y]);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read();

    for(int i=1;i<=n;i++)
        b[i]=read();
    memset(f,0x3f,sizeof(f));
    f[1]=0;
    q[head=tail=1]=1;
    for(int i=2;i<=n;i++)
    {
        while(head<tail&&calc(q[head],q[head+1])<a[i])
            head++;
        f[i]=f[q[head]]+a[i]*b[q[head]];
        while(head<tail&&calc(q[tail],i)<calc(q[tail-1],q[tail]))
            tail--;
        tail++;
        q[tail]=i;
    }
    cout<<f[n];
}
319C

 

任选起点和终点,使得必经边最多,问最多有几个必经边

不难发现只有割边才会是必经边,对边双连通分量缩点变成森林后,问题转化成了求直径,就做完了

有一个o(n)一遍dfs求树的直径的做法:如果我有maxx[x]表示x向子树内的最长链的长度,maxxx[x]表示次长链的长度,这俩必须不共享一个儿子,那么答案就是max(maxx[x]+maxxx[x]),表示最长的直径一定有一个lca,在lca处用maxx和maxxx来更新答案。那么迭代的时候,处理完儿子后用maxx[y]+1来迭代maxx[x]即可



#include<bits/stdc++.h>
using namespace std;
int read()
{
    int x;scanf("%d",&x);return x;
}
int n,m,c[600010],dcc,head[300010],tot,bridge[600010];
int dfn[300010],low[300010],num;
struct edge
{
    int y,next;
}e[600010];
vector<int>ee[300010];
void dfs(int x)
{
    c[x]=dcc;
    for(int i=head[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(c[y]||bridge[i])
            continue;
        dfs(y);
    }
}
void tarjan(int x,int in_edge)
{
    dfn[x]=low[x]=++num;
    for(int i=head[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(!dfn[y])
        {
            tarjan(y,i);
            low[x]=min(low[x],low[y]);
            if(low[y]>dfn[x])
                bridge[i]=bridge[i^1]=1;
        }
        else if(i!=(in_edge^1))
            low[x]=min(low[x],dfn[y]);
    }
}
void add(int x,int y)
{
    tot++;
    e[tot].y=y;
    e[tot].next=head[x];
    head[x]=tot;
}
int ans,maxx[300010],maxxx[300010];
void work(int x,int fa)
{
    for(auto y:ee[x])
    {
        if(y==fa)
            continue;
        work(y,x);
        int a[3]={maxx[x],maxxx[x],maxx[y]+1};
        sort(a,a+3);
        maxx[x]=a[2];maxxx[x]=a[1];
    }
    ans=max(ans,maxx[x]+maxxx[x]);
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();m=read();
    tot=1;
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        add(x,y);
        add(y,x);
    }
    tarjan(1,0);
    for(int i=1;i<=n;i++)
    {
        if(!c[i])
        {
            dcc++;
            dfs(i);
        }
    }
    for(int i=2;i<=tot;i++)
    {
        int x=e[i].y,y=e[i^1].y;
        if(c[x]==c[y])
            continue;
        ee[c[x]].push_back(c[y]);
    }
    work(1,0);
    cout<<ans;
}
1000E

 

 

有一堆柱子,让柱子高度相同。单点增需要花费A,单点减需要花费R,一加一减需要花费M

 

hi的顺序不影响结果,不妨给hi排序。如果A+R是小于M的,则操作3是没用的,不如操作1+操作2,可以令M=A+R。

 

如果最终高度小于最小h,那很没必要了,一定可以去掉n次操作二使得最终高度都增加1,且满足条件。同理,最终高度也不会高于最高h

 

高度在中间的时候,维护比他小的要变大多少,比他大的要变小多少,这块可以用操作3,剩下只剩变大或者变小,贪心地使用操作1或者操作2即可。高度从小变大会先变小再变大,是一个 凹函数。因此三分即可。

 

但是话又说回来了,做一个M=min(M,A+R)后,操作1,2,3可以不同时执行。要么只有13,要么只有2 3 ,只有1 2 等价于只有3,也有可能只有1,有可能只有2。因此最后变成的数字要么是平均数左右位置,要么是某个ai,可以做到on

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int read()
{
    int x;scanf("%d",&x);return x;
}
ll n,A,R,M;
ll a[100010];
ll suml,sumr,ans=1e18;
ll ask(ll v)
{
    ll l=0,r=0;
    for(int i=1;i<=n;i++)
        if(a[i]<=v)
            l+=v-a[i];
        else
            r+=a[i]-v;
    if(l<=r)
        return l*M+(r-l)*R;
    else
        return r*M+(l-r)*A;
}
int main()
{
    // freopen("1.in","r",stdin);
    n=read();A=read();R=read();M=read();
    M=min(A+R,M);
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        sumr+=a[i];
    }
    sort(a+1,a+1+n);

    ans=min(ans,ask((sumr+n-1)/n));
    ans=min(ans,ask(sumr/n));

    
    for(int i=1;i<=n;i++)
    {
        suml+=a[i-1];
        sumr-=a[i];
        ll t=0,l=a[i]*(i-1)-suml,r=sumr-a[i]*(n-i);
        // 要加l,要减r
        if(l<=r)
            ans=min(ans,l*M+(r-l)*R);
        else
            ans=min(ans,r*M+(l-r)*A);
    }
    cout<<ans;
}
1355E

 

给定联通图,图给你了,边权也给了,但是还没分配。现在从a走到b,从b走到c,问怎么分配然后怎么走能让价格最小。

感觉做过这种套路的最短路,考虑应该有一段路是共用的,设为x,也就是a->x->b->x->c,这样最小化a到x的路径数量,b到x的路径数量,c到x的路径数量变得非常正确。如果直接最小化a到b的数量,则b到c的路径数量是难以掌控的。

如果有了d1,d2,d3表示a到x的最小路径数量,b到x的最小路径数量和c到x的最小路径数量,那么肯定是拿边权最小的d1,d2,d3个,并且让前d2小的作为b到x的路径边权,可以使得边权和最小。d1,d2,d3可以跑三次bfs最短路得到。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int read()
{
    int x;scanf("%d",&x);return x;
}
int n,m,a,b,c,d[200010][4],vis[200010][4],now;
vector<int>e[200010];
ll p[200010],ans;
queue<int>q;
void bfs(int x)
{
    vis[x][now]=1;
    q.push(x);
    while(q.size())
    {
        x=q.front();
        q.pop();
        for(auto y:e[x])
        {
            if(vis[y][now])
                continue;
            d[y][now]=d[x][now]+1;
            vis[y][now]=1;
            q.push(y);
        }
    }
}
void work()
{
    n=read();m=read();a=read();b=read();c=read();
    for(int i=1;i<=n;i++)
        e[i].clear();
    for(int i=1;i<=m;i++)
        p[i]=read();
    sort(p+1,p+1+m);
    for(int i=1;i<=m;i++)
        p[i]=p[i-1]+p[i];
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        e[x].push_back(y);
        e[y].push_back(x);
    }
    for(int i=1;i<=n;i++)
        vis[i][1]=vis[i][2]=vis[i][3]=0;
    now=1;d[a][1]=0;
    bfs(a);
    now=2;d[b][2]=0;
    bfs(b);
    now=3;d[c][3]=0;
    bfs(c);
    ll ans=1e18;
    for(int x=1;x<=n;x++)
    {
        if(d[x][1]+d[x][2]+d[x][3]>m)
            continue;
        ans=min(ans,p[d[x][1]+d[x][2]+d[x][3]]+p[d[x][2]]);
    }
    cout<<ans<<'\n';
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1343E

 

 

有向图,给你1到i的最短距离di。在每个位置要么往d更大的地方走,要么往d更小的地方走,要么停下来。后两种操作只能执行一次,问从i出发能到达的所有j中,dj最小是多少。

第一步当然是做一个bfs最短路,接下来开个大根堆,每次拿出当前d最大的x,枚举出边y来更新ans[x]即可:要么d[y]>d[x],可以用ans[y]更新ans[x],要么d[y]<d[x],可以用d[y]来更新ans[x]

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int read()
{
    int x;scanf("%d",&x);return x;
}
int d[200010],ans[200010],n,m;
vector<int>e[200010];
priority_queue<pair<int,int>>q;
queue<int>que;
void work()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        e[i].clear();
        d[i]=0x3f3f3f3f;
    }

    for(int i=1;i<=m;i++)
    {
        int x=read();
        e[x].push_back(read());
    }
    d[1]=0;
    que.push(1);
    while(que.size())
    {
        int x=que.front();
        que.pop();
        for(auto y:e[x]){
            if(d[y]>d[x]+1)
            {
                d[y]=d[x]+1;
                que.push(y);
            }
        }
        ans[x]=d[x];
        q.push({d[x],x});
    }
    while(q.size())
    {
        int x=q.top().second;
        q.pop();
        for(auto y:e[x])
            if(d[y]>d[x])
                ans[x]=min(ans[x],ans[y]);
            else
                ans[x]=min(ans[x],d[y]);
    }
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<' ';
    cout<<'\n';
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1472G

 

 

有q次交换操作,每次交换li和ri位置的两个ai,问你拿出来若干个数字,组成的交替和最大是多少。可以不拿

首先思考给你一个数组,交替和最大是多少。可以如果a[i]-a[i+1]>0我就把a[i]-a[i+1]算在答案里,否则不算,其中a[n]和a[n+1]也算上去。




a[n+1]=0;
for(int i=0;i<=n;i++)
ans=ans+max(0,a[i]-a[i+1]);

具体来说,如果a[i]之前已经被放了一个-a[i],我们就把-a[i]这个符号强加到a[i+1]身上,也就是???-a[i]变成???-a[i+1],这样得到的和会增大a[i]-a[i+1]。a[i]之前如果没被选过,那么令+a[i],-a[i+1],和可以增大a[i]-a[i+1],也就是????变成????+a[i]-a[i+1]。a[i]之前不可能被选为+a[i]。

这样交换a[l]也很简单了,先把a[l]和a[r]之前造成的贡献减掉(max(0,a[l-1]-a[l]),max(0,a[l]-a[l+1]),max(0,a[r-1]-a[r]),max(0,a[r]-a[r+1]),再swap,再把现在造成的贡献加上去。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int read()
{
    int x;scanf("%d",&x);return x;
}
int n,q,a[3000010];
ll ans;
void work()
{
    ans=0;
    n=read();q=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    a[n+1]=0;
    for(int i=0;i<=n;i++)
        ans=ans+max(0,a[i]-a[i+1]);

    cout<<ans<<'\n';
    for(;q;q--)
    {
        int l=read(),r=read();
        ans=ans-max(0,a[l-1]-a[l]);
        ans=ans-max(0,a[l]-a[l+1]);
        if(l+1!=r)
            ans=ans-max(0,a[r-1]-a[r]);
        ans=ans-max(0,a[r]-a[r+1]);
        swap(a[l],a[r]);
        ans=ans+max(0,a[l-1]-a[l]);
        ans=ans+max(0,a[l]-a[l+1]);
        if(l+1!=r)
            ans=ans+max(0,a[r-1]-a[r]);
        ans=ans+max(0,a[r]-a[r+1]);
        cout<<ans<<'\n';
    }
}
int main()
{
    // freopen("1.in","r",stdin);
    for(int t=read();t;t--)
        work();
}
1420C2

 

1538G

 x个红色,y个蓝色,问a红b蓝或者a蓝b红最多能做几个

 

如果b==a就很好了,直接输出min(n,m)/a即可

 

否则先大胆选

posted @ 2024-01-01 17:27  zzuqy  阅读(116)  评论(0)    收藏  举报