11 月杂题题解

B1031 T3 区间

波神说这很板,破防了。

考虑如何维护区间的并。

离线,然后扫描线,并把询问挂到右端点。

从左往右,考虑加入一条线段的影响。

假设现在加入第 \(i\) 条线段,对于 \(j\leq i\),维护 \(f(j)\) 表示线段 \(j\sim i\) 的并的长度。

某段位置上一次是在 \(lst\) 被覆盖,那么只要询问左端点在 \(lst+1 \sim i\) 这个范围内,当前这段就会产生贡献,其实就是个区间加的操作。

用树状数组 + ODT 维护即可。

那么对于一个询问 \(ql,qr\),目前就可以算出以 \(qr\) 为右端点,左端点在 \([ql,qr]\) 的答案。

那么如何算出 \(\sum\limits_{ql\leq r\leq qr} \sum\limits_{ql\leq l\leq r} ans(l,r)\) 呢?

你发现这其实是个历史版本和。

由于这题 \(n\leq 8\times 10^5,q\leq 2\times 10^6\),线段树多半要寄。

其实历史版本和某些情况可以用树状数组维护,还特别方便。

先考虑树状数组如何区间求和区间修改,详见

假设现在是 \(i\) 时刻(加入第 \(i\) 条线段),当前位置为 \(j\),这个位置之前有 \(c\) 次修改,第 \(k\) 次修改时间为 \(t_k\),加的权值是 \(v_k\)(加入第 \(t_k\) 条线段的时候让某个区间加了 \(v_k\)),那么位置 \(j\) 的贡献为:

\(\sum (i-t_k+1)\times v_k\)

即:

\((i+1)\sum v_k-\sum t_k\times v_k\)

分别维护 \(\sum v_k\)\(\sum t_k\times v_k\) 即可。

注意这题对于一个区间 \([l,r]\) 长度是 \(r-l\) 的,直接令所有左端点 +1,区间长度还是按 \(r-l+1\) 算。

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

const int N=8e5+5,Q=2e6+5,inf=2e9,p=998244353;
int n,m,L[N],R[N],ans[Q];
struct query{int l,r,id;} q[Q];
struct node{
    int l,r,lst;
    bool operator<(const node&x)const{
        return l==x.l?r<x.r:l<x.l;
    }
};
set<node> s;
void inc(int &x,int y) {if((x+=y)>=p) x-=p;}
int qpow(int a,int b) {int r=1;for(;b;b>>=1,a=a*a%p) if(b&1) r=r*a%p;return r;}

struct BIT{
    int c1[N],c2[N];
    void upd(int x,int v) {for(int V=x*v%p;x<=n;x+=x&-x) inc(c1[x],v),inc(c2[x],V);}
    int sum(int x) {int s1=0,s2=0;for(int y=x;y;y^=y&-y) inc(s1,c1[y]),inc(s2,c2[y]);return ((x+1)*s1-s2+p)%p;}
    int qsum(int l,int r) {return (sum(r)-sum(l-1)+p)%p;}
}tr1,tr2;

void upd(int l,int i,int v)
{
    tr1.upd(l,v),tr1.upd(i+1,-v);
    tr2.upd(l,v*i%p),tr2.upd(i+1,-v*i%p);
}

inline int rd()
{
    int x=0;char c=getchar();
    for(;!isdigit(c);c=getchar());
    for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
    return x;
}

signed main()
{
    n=rd(),m=rd();
    for(int i=1;i<=n;i++) L[i]=rd()+1,R[i]=rd();
    for(int i=1;i<=m;i++) q[i]={rd(),rd(),i};
    sort(q+1,q+1+m,[&](auto a,auto b){return a.r<b.r;});
    s.insert({-inf,inf,0});s.insert({inf,inf,0});
    for(int i=1,j=1;i<=n;i++)
    {
        int l=L[i],r=R[i];
        auto it=s.lower_bound({l,r,0});--it;
        auto [_l,_r,lst]=*it;
        if(l<=_r)
        {
            s.erase(it);
            s.insert({_l,l-1,lst});
            if(r<_r) upd(lst+1,i,r-l+1),s.insert({r+1,_r,lst});
            else upd(lst+1,i,_r-l+1);
        }
        for(it=s.lower_bound({l,r,0});it!=s.end()&&it->l<=r;++it,s.erase(prev(it)))
        {
            auto [_l,_r,lst]=*it;
            if(r<_r) upd(lst+1,i,r-_l+1),s.insert({r+1,_r,lst});
            else upd(lst+1,i,_r-_l+1);
        }
        s.insert({l,r,i});
        while(j<=m&&q[j].r==i)
        {
            auto [l,r,id]=q[j];
            int cnt=(r-l+1)*(r-l+2)/2%p;
            ans[id]=(i+1)*tr1.qsum(l,r)-tr2.qsum(l,r);
            ans[id]=(ans[id]%p+p)%p*qpow(cnt,p-2)%p;
            j++;
        }
    }
    for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
}

P9194 Triples of Cows P

删除一个点影响太大,考虑增加一些额外结点。

把第 \(i\) 条边拆为 \((u,n+i),(v,n+i)\)

记原来的点是黑点,新加的点为白点。

不难发现新生成的图还是一棵树,且一个白点所连的所有黑点在原图两两有边。

考虑删除一个黑点 \(u\),就是让 \(u\) 的子白点 \(v\) 合并到父亲白点 \(fa_u\) 上。

并查集维护即可。

考虑计算答案。不妨三元组 \((a,b,c)\) 变为五元组 \((a,x,b,y,c)\),其中 \(x,y\) 为白点,其余为黑点。那么有以下情况:

  • \(x=y\)

\(f_x\) 为白点 \(x\) 的黑儿子个数,随便选三个即可,贡献 \((f_x+1)\times f_x \times (f_x-1)\)

  • \(x,y\)\(b\) 的儿子。

枚举 \(x,y\),贡献 \(\sum\limits_{x,y\in son_b,x\neq y} f_x\times f_y=(\sum\limits_{x} f_x)^2-\sum\limits_{x} f_x^2\)

  • \(x\)\(b\) 的父亲,\(y\)\(b\) 的儿子。

贡献为 \(2\times f_x\sum\limits_{b\in son_x} \sum\limits_{y\in son_b} f_y\),前面为选 \(a\) (除 \(b\) 以外的儿子和 \(x\) 的父亲)的方案,后面为选 \(c\) 的方案。

\(i\) 为黑点,定义 \(g_i=\sum\limits_{j\in son_i} f_j\)

\(i\) 为白点,定义 \(h_i=\sum\limits_{j\in son_i} g_i\)

考虑对上面的所有情况贡献求和,那么答案为:

\(\sum\limits_{1\leq i\leq n} g_i^2+\sum\limits_{n<j<2n} (f_i+1)\times f_i\times (f_i-1)-f_i^2+2\times f_i\times h_i\)

删除一个点 \(u\),受影响的有子结点 \(v\),父结点 \(x\),父父结点 \(y\),父父父结点 \(z\)

合并 \(v \rightarrow x\),维护 \(f,g,h\) 值即可。

尤其注意合并时要剔除 \(f_v\)\(t_x\) 的贡献。

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

const int N=4e5+5;
int n,ans,f[N],g[N],h[N],fa[N],F[N];
vector<int> G[N];
int find(int x) {return x==F[x]?x:F[x]=find(F[x]);}
int calc(int i) {if(!i) return 0;return (f[i]+1)*f[i]*(f[i]-1)-f[i]*f[i]+2*f[i]*h[i];}

void dfs(int u)
{
    for(int v:G[u])
    {
        if(v==fa[u]) continue;
        fa[v]=u,dfs(v);
        if(u<=n) g[u]+=f[v];
        else f[u]++,h[u]+=g[v];
    }
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int u,v;cin>>u>>v;
        G[u].push_back(n+i),G[n+i].push_back(u);
        G[v].push_back(n+i),G[n+i].push_back(v);
    }
    dfs(n);
    for(int i=1;i<=n;i++) ans+=g[i]*g[i];
    for(int i=n+1;i<2*n;i++) ans+=calc(i),F[i]=i;
    cout<<ans<<'\n';
    for(int i=1;i<n;i++)
    {
        int u=i,x=find(fa[u]),y=fa[x],z=find(fa[y]);
        ans-=g[u]*g[u]+calc(x)+g[y]*g[y]+calc(z);
        f[x]--,g[y]--,h[z]--;
        for(int v:G[u])
        {
            if(v==fa[u]) continue;
            F[v]=x;
            ans-=calc(v);
            f[x]+=f[v];
            h[x]-=f[v],h[x]+=h[v];
            g[y]+=f[v],h[z]+=f[v];
        }
        ans+=calc(x)+g[y]*g[y]+calc(z);
        cout<<ans<<'\n';
    }
}

P7418 Counting Graphs P

难难难难难!!!

属实是计数属性拉满了。

参考这篇题解。

Part 1

设一个点奇最短路和偶最短路组成的二元组为 \((x,y)\),钦定 \(x<y\)

那么有两种方案得到 \((x,y)\)

  1. 某个 \((x-1,y-1)\) 的点向 \((x,y)\) 连了边。
  2. 某个 \((x-1,?)\) 和某个 \((?,y-1)\) 的向 \((x,y)\) 连了边,不难发现两点为 \((x-1,y+1),(x+1,y-1)\)(用点 \((x,y)\) 更新其最短路),当 \(x=y+1\) 时有 \((x+1,y-1)=(y-1,x+1)=(x,y)\)

Part 2

考虑 dp。

可以发现第一种有很强阶段性,第二种 \(x+y\) 是相同的。

考虑以 \(x+y\) 为第一层阶段,\(x\) 为第二层阶段进行 dp。

那么一个状态 \((x,y)\) 的前继状态为 \((x-1,y+1)\),后继状态为 \((x+1,y-1)\)

每个点可以只用第一种/第二种满足,或者同时满足。只用第二种满足是特殊的,因为会影响前后状态,拿出来单独考虑。

\(f(x,y,p)\) 表示当前 dp 到 \((x,y)\),有 \(p\) 个只用第二种满足。

\((x,y)\) 的点有 \(s1\) 个,\((x-1,y-1)\) 的点有 \(s2\) 个。

设有 \(q\) 个点只用方案一满足。

\(f(x,y,p)=\sum\limits_{0\leq q\leq s1-p} \binom{s1-p}{q} (2^{s2}-1)^{s1-p}g(x,y,q)\)

\(s1-p\) 个点用到第一种满足的,向前面连边,方案 \((2^{s2}-1)^{s1-p}\)

\(g(x,y,q)\) 表示从 \((x-1,y+1)\) 往后连边,让 \(s1-q\) 个点有边的方案。

考虑转移 \(g(x,y,p)\),枚举上一状态有多少点需要往后连边,设有 \(q\) 个点。设 \((x-1,y+1)\) 的点有 \(s2\) 个。

\(g(x,y,p)=\binom{s1}{p}\sum\limits_{0\leq q\leq s2} h(s2,q,s1-p)\times f(x-1,y+1,q)\)

其中 \(h(s2,q,s1-p)\) 表示在大小为 \(s2\) 的集合和大小为 \(s1-p\) 的集合之间连边,保证 \(s2\) 里有 \(q\) 个点度数至少为 \(1\),大小为 \(s1-p\) 的集合所有点度数至少为 \(1\) 的方案。

考虑计算 \(h(i,j,k)\),钦定 \(j\) 个点中有 \(p\) 个点一定不连边,容斥即可。

\(h(i,j,k)=\sum\limits_{0\leq p\leq j} (-1)^j\binom{j}{p}(2^{i-p}-1)^k\)

Part 3

考虑如何计算答案。

显然把互不影响的几段的方案乘起来即可。

实际上漏了一种情况,二元组 \((x,x+1)\),如果仅用方案二满足,这些二元组的后继状态可以是这些二元组之间互相连边。

设这些二元组有 \(s\) 个,那么方案为:

\(\sum\limits_{0\leq i\leq s} d(s,i)\times f(x,x+1,i)\)

其中 \(d(s,i)\) 表示在大小为 \(s\) 的集合中 \(i\) 个点的后继状态是互相连边。

考虑转移 \(d(i,j)\),钦定 \(j\) 个点中有 \(k\) 个点一定不连边,容斥即可。

\(\large{d(i,j)=\sum\limits_{0\leq k\leq j} (-1)^k\binom{j}{k}2^{\frac{(i-k)(i-k+1)}{2}}}\)

复杂度 \(O(n^4)\)

注意特判二分图,每个点都只有 \((x,inf)\) 二元组的情况,那么转移只有 \((x,inf) \rightarrow (x+1,inf)\)

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

const int N=205,M=10005,mod=1e9+7;
int qpow(int a,int b) {int r=1;for(;b;b>>=1,a=a*a%mod) if(b&1) r=r*a%mod;return r;}
void inc(int &x,int y) {if((x+=y)>=mod) x-=mod;}
void mul(int &x,int y) {if((x*=y)>=mod) x%=mod;}
int topos(int x) {return (x+mod)%mod;}

int pw[M],pwk[N][N],C[N][N],h[N][N][N],d[N][N];
void init()
{
    for(int i=0;i<=200;i++) {C[i][0]=C[i][i]=1;for(int j=1;j<i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;}
    pw[0]=1;for(int i=1;i<=10000;i++) pw[i]=pw[i-1]*2%mod;
    for(int i=0;i<=200;i++) {pwk[i][0]=1;for(int j=1;j<=200;j++) pwk[i][j]=pwk[i][j-1]*(pw[i]-1)%mod;}
    for(int i=0;i<=100;i++)
        for(int j=0;j<=i;j++)
            for(int k=0;k<=100;k++)
                for(int p=0;p<=j;p++)
                    inc(h[i][j][k],topos(((p&1)?-1:1)*C[j][p]%mod*pwk[i-p][k]%mod));
    for(int i=0;i<=100;i++)
        for(int j=0;j<=i;j++)
            for(int k=0;k<=j;k++)
                inc(d[i][j],topos(((k&1)?-1:1)*C[j][k]*pw[(i-k)*(i-k+1)/2]%mod));
}

vector<int> G[N];
int n,m,ans;
int ps[N],ds[N][2],f[N][N][N],g[N][N][N],cnt[N][N];
void solve()
{
    cin>>n>>m;
    ans=1;
    for(int i=1;i<=n;i++) G[i].clear();
    for(int i=0;i<=n*2;i++) for(int j=0;j<=n*2;j++) {cnt[i][j]=0;for(int k=0;k<=n;k++) f[i][j][k]=g[i][j][k]=0;}
    for(int i=1;i<=m;i++)
    {
        int x,y;cin>>x>>y;
        G[x].push_back(y),G[y].push_back(x);
    }
    memset(ds,0x3f,sizeof(ds));
    queue<pair<int,int>> q;
    ds[1][0]=0;q.push({1,0});
    while(!q.empty())
    {
        auto [x,w]=q.front();q.pop();
        for(int y:G[x]) if(ds[y][w^1]>=1e9) ds[y][w^1]=ds[x][w]+1,q.push({y,w^1});
    }
    for(int i=1;i<=n;i++) {ps[i]=i;if(ds[i][0]>ds[i][1]) swap(ds[i][0],ds[i][1]);}
    if(ds[n][1]>=1e9)//特判
    {
        for(int i=1;i<=n;i++) cnt[ds[i][0]][0]++;
        for(int i=1;i<n;i++) mul(ans,pwk[cnt[i-1][0]][cnt[i][0]]);
        cout<<ans<<'\n';return;
    }
    sort(ps+1,ps+1+n,[](int i,int j){int si=ds[i][0]+ds[i][1],sj=ds[j][0]+ds[j][1];return si==sj?ds[i][0]<ds[j][0]:si<sj;});
    for(int i=1,j;i<=n;i=j)
    {
        j=i;
        int x=ds[ps[i]][0],y=ds[ps[i]][1];
        while(j<=n&&ds[ps[j]][0]==x&&ds[ps[j]][1]==y) j++;
		int s1=j-i,s2=cnt[x-1][y+1];cnt[x][y]=s1;
		if(!s2) g[x][y][i==1?0:s1]=1;//i=1 不能往前连边
		else
		{
			for(int p=0;p<=s1;p++)
				for(int q=0;q<=s2;q++)
                    inc(g[x][y][p],h[s2][q][s1-p]*f[x-1][y+1][q]%mod);
            for(int p=0;p<=s1;p++) mul(g[x][y][p],C[s1][p]);
		}
		s2=cnt[x-1][y-1];
        for(int p=0;p<=s1;p++)
            for(int q=0;q<=s1-p;q++)
                inc(f[x][y][p],C[s1-q][p]*pwk[s2][s1-p]%mod*g[x][y][q]%mod);
        if(j>n||ds[ps[j]][0]!=x+1||ds[ps[j]][1]!=y-1)//i~j-1 这一段往后没有后继状态,作为单独一段贡献答案
        {
            if(x+1==y) {int s=0;for(int i=0;i<=s1;i++) inc(s,f[x][y][i]*d[s1][i]%mod);mul(ans,s);}
            else mul(ans,f[x][y][0]);
        }
    }
    cout<<ans<<'\n';
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int t;cin>>t;init();
    while(t--) solve();
}

ARC105E Keep Graph Disconnected

赛时没调出来的傻逼。

考虑最后的局面一定是只有一个包含 \(1\) 的连通块和一个包含 \(n\) 的连通块。

设最后 \(1\) 所属连通块大小为 \(x\),那么一共能连 \(\frac{n(n-1)}2{}-m-x(n-x)\) 条边。

判断这个数的奇偶即可,奇数先手赢,否则后手赢。

先把常量踢出来,也就是只关心 \(x(n-x)\) 的奇偶。

\(n\) 为奇数,\(x(n-x)\) 一定为偶数,特判掉。

考虑 \(n\) 为偶数。

定义奇连通块为点大小为奇数的连通块,偶类似。

如果剩偶数个奇连通块,那么这些奇连通块之间可以互相抵消影响,无法改变局面,按 \(1\) 一开始所属连通块大小计算答案。

否则,先手可以决定是否改变奇偶性,并变更为上面的情况,先手必胜。

一个绝妙的判断剩余奇连通块个数的方法:判断 \(1,n\) 所属连通块大小之和的奇偶性。

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

const int N=1e5+5;
int n,m,fa[N],sz[N];
int find(int x) {return x==fa[x]?x:find(fa[x]);}
void merge(int u,int v)
{
    u=find(u),v=find(v);
    if(u==v) return;
    if(sz[u]<sz[v]) swap(u,v);
    fa[v]=u,sz[u]+=sz[v];
}

void solve()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) fa[i]=i,sz[i]=1;
    for(int i=1,x,y;i<=m;i++) cin>>x>>y,merge(x,y);
    int u=find(1),v=find(n),x=n*(n-1)/2-m;
    if(n&1) {(x&1)?puts("First"):puts("Second");return;}
    if((sz[u]&1)==(sz[v]&1)) ((x-sz[u]*(n-sz[v]))&1)?puts("First"):puts("Second");
    else puts("First");
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int t;cin>>t;
    while(t--) solve();
}

麻将机(code jam world finals 2020 E)

改的汗流浃背。

首先扒原题你翻译错了,这就是 cqbz。应该是每个步骤使用至少一次。

考虑建图,对于一个操作就向两个字符点之间连一条有向边。

在连出来的这个图中,若这个点的字符在 \(S\) 中出现,则为黑点,否则为白点。

我们只关心一个字符是否出现,而不关心其数量,故最大点数为 62。

每个点一旦被执行了一次出边,那么接着再执行其余所有出边将不会有任何损失。

而对于白点,可以一开始就执行其所有出边,而无损失。

所以一个方案合法,当且仅当每个有出边的黑点执行至少一条出边。(其余边是否使用不做强制限制)

定义图中的路径集合 \(L\) 的权值 \(W=|L|-s\),其中 \(s\) 表示有多少白点作为至少一条路径的结尾。

可以证明:

  • 若存在一个覆盖了所有黑点的 \(L\) 权值为 \(X\),那么必然存在一个造成损失不超过 \(X\) 的方案。

可以找到 \(|L|-X\) 条以不同白点结尾的路径,这些路径不会造成损失。

其余 \(X\) 条路径每条最少造成 \(1\) 的损失。

  • 反过来,若存在一个损失不超过 \(X\) 的方案,那么必然存在一个覆盖了所有黑点的路径集合 \(L\),权值为 \(X\)

问题转化为求覆盖所有黑点的 \(L\) 的最小权值。

注意到一条路径若经过了一个点,那么可以遍历完这个点所在的强连通分量并返回起点,而不造成任何损失。所以可以先缩点,把原图缩成 DAG。

一个细节是我们缩点时只考虑黑点。

那么就是求最小链覆盖,很典的一个问题。

设二分图左部 \(A\),包含所有需要覆盖的强连通分量。

右部 \(B\),一部分包含所有需要覆盖的强连通分量,另一部分是所有白点。

设左部点为 \(u\),右部点为 \(v\)

  • 若原图中 \(u\) 能到达 \(v\),则连一条边。
  • \(u\) 是一个单点,且没有出度,向 \(B\) 中的自己连一条边。

最后 \(|A|-flow\) 就是最小权值,也是有多少黑点作为至少一条路径的结尾的数量。

这样建图为什么是对的,你考虑一个左部点如果能流过去,那它必然是作为路径中的结点的,那么剩下的点就是作为路径结尾的结点。

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

const int N=300,M=10005;
int t,n,k,a[N],b[N],d[N],f[N][N];//b[i]=1:black  b[i]=2:white
int S,T,tot=1,pre[N],cur[N],lv[N];
struct edge{int to,nxt,w;} e[M];
vector<int> G[N];
map<char,int> mp;
void add(int u,int v,int w)
{
    e[++tot]={v,pre[u],w};pre[u]=tot;
    e[++tot]={u,pre[v],0};pre[v]=tot;
}

int dn,top,cnt,dfn[N],low[N],stk[N],bel[N],sz[N];
void tarjan(int u)//use tarjan,since i don't know how to use other trivial algorithm!
{
    dfn[u]=low[u]=++dn;
    stk[++top]=u;
    for(int v:G[u])
    {
        if(b[v]!=1) continue;
        if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
        else if(!bel[v]) low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        int v;++cnt;
        do{
            v=stk[top--];
            bel[v]=cnt,sz[cnt]++;
        }while(u!=v);
    }
}

int bfs()
{
    memset(lv,0,sizeof(lv));
    memcpy(cur,pre,sizeof(cur));
    queue<int> q;q.push(S);lv[S]=1;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=pre[u];i;i=e[i].nxt)
        {
            int v=e[i].to,w=e[i].w;
            if(!lv[v]&&w>0) lv[v]=lv[u]+1,q.push(v);
        }
    }
    return lv[T];
}

int dfs(int u=S,int flow=1e9)
{
    if(u==T) return flow;
    int lev=flow;
    for(int i=cur[u];i&&lev;i=e[i].nxt)
    {
        cur[u]=i;
        int v=e[i].to,w=e[i].w;
        if(w>0&&lv[v]==lv[u]+1)
        {
            int out=dfs(v,min(w,lev));
            e[i].w-=out,e[i^1].w+=out;
            lev-=out;
        }
    }
    return flow-lev;
}

void clear()
{
    #define mem(a) memset(a,0,sizeof(a))
    dn=top=cnt=0;
    mem(a),mem(b),mem(d),mem(f),mem(e),mem(dfn),mem(low),mem(bel),mem(sz),mem(pre);
}

void solve()
{
    clear();
    string s;
    cin>>s;cin>>k;
    int ans=0;
    for(int i:s) a[mp[i]]=1;
    for(int i=1;i<=n;i++) ans+=a[i];
    for(int i=1;i<=n;i++) G[i].clear();
    while(k--)
    {
        cin>>s;
        int u=mp[s[0]],v=mp[s[1]];
        f[u][v]=1,G[u].push_back(v);
        a[u]?b[u]=1:b[u]=2;
        a[v]?b[v]=1:b[v]=2;
    }
    for(int i=1;i<=n;i++) if(b[i]==1&&!dfn[i]) tarjan(i);
    S=0,T=n*3+1,tot=1;
    for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) f[i][j]|=f[i][k]&f[k][j];
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(f[i][j]&&bel[i]&&bel[i]!=bel[j]) add(bel[i],bel[j]+n,1),d[bel[i]]++;
    for(int i=1;i<=n;i++) if(b[i]==1) for(int j=1;j<=n;j++) if(b[j]==2&&f[i][j]) add(bel[i],j+2*n,1),d[bel[i]]++;
    for(int i=1;i<=cnt;i++) if(sz[i]==1&&!d[i]) add(i,i+n,1);
    for(int i=1;i<=cnt;i++) add(S,i,1),add(i+n,T,1);
    for(int i=1;i<=n;i++) if(b[i]==2) add(i+2*n,T,1);
    while(bfs()) ans+=dfs();
    cout<<ans-cnt<<'\n';
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    for(int i='0';i<='z';i++) if(isdigit(i)||isalpha(i)) mp[i]=++n;
    cin>>t;for(int i=1;i<=t;i++) solve();
}

CF152E Garden

\(k\leq 7\),首先考虑状压。

注意最后一定会连成一棵树,定义状态 \(f(i,j,S)\) 表示以 \((i,j)\) 为根的连通块包含了 \(S\) 状态的关键点的最小代价。

转移一:\(f(i,j,S)=\min\{f(i,j,T)+f(i,j,S \operatorname{xor} T)+a_{i,j} \}\),即枚举子集。

转移二:\(f(i,j,S)=\min\{f(x,y,S)+w(i,j,x,y) \}\),其中 \(w(i,j,x,y)\) 表示从点 \((i,j)\) 到点 \((x,y)\) 的最小代价。

这个 \(w\) 看似很难求,但其实这是个网格图,且 \(n\times m \leq 200\),对于一个位置只需要用相邻的位置更新即可,重复这个过程 \(n\times m\) 次。

可以发现一些错误的转移只会令答案更劣,所以这样是对的。

输出方案平凡。

(这玩意儿好像叫斯坦纳树?)

双倍经验

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

const int N=105,M=(1<<7)+5;
const int dx[4]={0,1,0,-1};
const int dy[4]={1,0,-1,0};
pair<int,int> lst[N][N][M];
int n,m,k,a[N][N],x[10],y[10],f[N][N][M],p[N][N][M];

void print(int x,int y,int S)
{
    a[x][y]=0;
    if(p[x][y][S]==1) print(x,y,lst[x][y][S].first),print(x,y,lst[x][y][S].second);
    if(p[x][y][S]==2) print(lst[x][y][S].first,lst[x][y][S].second,S);
}

int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j];
    memset(f,0x3f,sizeof(f));
    for(int i=0;i<k;i++) cin>>x[i]>>y[i],f[x[i]][y[i]][1<<i]=a[x[i]][y[i]];
    for(int S=0;S<1<<k;S++)
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                for(int T=S;T;T=(T-1)&S)
                {
                    int tmp=f[i][j][T]+f[i][j][S^T]-a[i][j];
                    if(tmp<f[i][j][S]) f[i][j][S]=tmp,p[i][j][S]=1,lst[i][j][S]={T,S^T};
                }
        for(int t=0;t<=n*m;t++)
            for(int x=1;x<=n;x++)
                for(int y=1;y<=m;y++)
                    for(int i=0;i<4;i++)
                    {
                        int tx=x+dx[i],ty=y+dy[i];
                        if(tx<0||tx>n||ty<0||ty>m) continue;
                        int tmp=f[tx][ty][S]+a[x][y];
                        if(tmp<f[x][y][S]) f[x][y][S]=tmp,p[x][y][S]=2,lst[x][y][S]={tx,ty};
                    }
    }
    int x,y,ans=1e9,S=(1<<k)-1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(f[i][j][S]<ans)
                ans=f[i][j][S],x=i,y=j;
    cout<<ans<<'\n';
    print(x,y,S);
    for(int i=1;i<=n;i++,cout<<'\n') for(int j=1;j<=m;j++) a[i][j]?putchar('.'):putchar('X');
}

P2619 Tree I

非常有意思的二分。

考虑二分一个权值 \(w\),另所有白边加上这个权值(可以为负)。

加完后跑 kruscal,看当前最多会用多少白边。

对于不能恰好满足的情况,即 \(w=x\)\(use>need\)\(w=x+1\)\(use<need\)

由于题目保证有解,当前情况一定是有一些黑边和白边权值相同,显然可以把一些白边替换为黑边保证合法。

双倍经验

三倍经验,需要判无解。

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

const int N=1e5+5;
int n,m,k,ans,res,cnt,fa[N];
struct edge{int u,v,w,c;} e[N];
int find(int x) {return fa[x]==x?fa[x]:fa[x]=find(fa[x]);}

void kruscal()
{
    sort(e+1,e+1+m,[&](edge a,edge b){return a.w==b.w?a.c<b.c:a.w<b.w;});
    for(int i=0;i<=n;i++) fa[i]=i;
    res=cnt=0;
    for(int i=1,c=0;i<=m;i++)
    {
        auto [u,v,w,col]=e[i];
        if(find(u)==find(v)) continue;
        fa[find(u)]=find(v),res+=w,cnt+=!col;
        if(++c==n-1) break;
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v>>e[i].w>>e[i].c;
    int l=-101,r=101;
    while(l<=r)
    {
        int mid=l+r>>1;
        for(int i=1;i<=m;i++) if(!e[i].c) e[i].w+=mid;
        kruscal();
        if(cnt>=k) l=mid+1,ans=res-k*mid;
        else r=mid-1;
        for(int i=1;i<=m;i++) if(!e[i].c) e[i].w-=mid;
    }
    cout<<ans<<'\n';
}

CF1163F Indecisive Taxi Fee

先求一遍 \(1 \rightarrow n\) 的最短路,设用到的边集为 \(E\)

假设当前修改的边为 \(i\)

显然答案为 经过这条边的最短路 和 不经过这条边的最短路 的最小值。

  • \(i\notin E\),答案为 \(\min(d_{1,u}+x+d_{v,n},d_{1,v}+x+d_{u,n},d_{1,n})\)

  • \(i\in E\),答案为 \(d_{1,n}-w_i+x\) 和 不经过这条边的最短路 的最小值。

难点在于求所有不经过某条 \(E\) 上边的最短路。

一个结论:

删掉任意一条边后,一定存在一条最短路有一个前缀(可能为空)与 \(E\) 重合,一个后缀(可能为空) 与 \(E\) 重合,中间部分都不在 \(E\) 上,且只经过一条非最短路树边。

证明详见 Alex_Wei 图论基础 1.5.2 or this

那么对于每个点 \(x\),可以求出 \(l_x\) 表示第一条 \(i\in E\) 的边,但不在 \(1\rightarrow x\) 的某条最短路上。\(r_x\) 表示最后一条 \(i\in E\) 的边,但不在 \(x\rightarrow n\) 的某条最短路上。

对于 \(E\) 上第 \(i\) 个点 \(x\),初始 \(l_x=i,r_x=i-1\)

以求 \(l_x\) 为例,对于一条在 \(1\rightarrow v\) 最短路的边 \((u,v)\)\(l_v=\min(l_v,l_u)\)。将点按 \(d_1\) 从小到大排序,枚举这个点的出边即可转移,注意不要用 \(E\) 上的边更新,因为存在一种最优情况满足 \(1\rightarrow x\) 只与 \(E\) 有一个前缀重合。

那么对于一条不在 \(E\) 上的边 \((u,v)\)\(d_{1,u}+w(u,v)+d_{v,n}\) 能影响到的在 \(E\) 上的边的范围是 \([l_u,r_v]\)\(d_{1,v}+w(u,v)+d_{u,n}\) 能影响到的在 \(E\) 上的边的范围是 \([l_v,r_u]\),扫描线维护即可。

说实话我觉得这个方法非常难理解,还是最短路树更好理解些。

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

const int N=2e5+5;
int n,m,q,len,p[N],l[N],r[N],t[N],ans[N];
int tot=1,u[N],v[N],w[N],pre[N];
vector<int> inc[N],cut[N];
multiset<int> s;
struct edge{int to,nxt,w;} e[N*2];
void add(int u,int v,int w) {e[++tot]={v,pre[u],w};pre[u]=tot;}

int d1[N],dn[N],vis[N];
void dijkstra(int s,int *d)
{
    for(int i=1;i<=n;i++) d[i]=1e18,vis[i]=0;
    priority_queue<pair<int,int>> q;
    q.push({d[s]=0,s});
    while(!q.empty())
    {
        int u=q.top().second;q.pop();
        if(vis[u]) continue;
        vis[u]=1;
        for(int i=pre[u];i;i=e[i].nxt)
        {
            int v=e[i].to,w=e[i].w;
            if(d[u]+w<d[v]) d[v]=d[u]+w,q.push({-d[v],v});
        }
    }
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>q;
    memset(l,0x3f,sizeof(l));
    for(int i=1;i<=m;i++) cin>>u[i]>>v[i]>>w[i],add(u[i],v[i],w[i]),add(v[i],u[i],w[i]);
    dijkstra(1,d1),dijkstra(n,dn);
    for(int u=1;u!=n;)
    {
        l[u]=len+1,r[u]=len;
        for(int j=pre[u],v;j;j=e[j].nxt) if(dn[u]==dn[v=e[j].to]+e[j].w) {u=v,p[j/2]=++len;break;}
    }
    l[n]=len+1,r[n]=len;
    for(int i=1;i<=n;i++) t[i]=i;
    sort(t+1,t+1+n,[](int a,int b){return d1[a]<d1[b];});
    for(int i=1;i<=n;i++) for(int u=t[i],j=pre[u],v;j;j=e[j].nxt) if(!p[j/2]&&d1[u]+e[j].w==d1[v=e[j].to]) l[v]=min(l[v],l[u]);
    sort(t+1,t+1+n,[](int a,int b){return dn[a]<dn[b];});
    for(int i=1;i<=n;i++) for(int u=t[i],j=pre[u],v;j;j=e[j].nxt) if(!p[j/2]&&dn[u]+e[j].w==dn[v=e[j].to]) r[v]=max(r[v],r[u]);
    for(int i=1;i<=m;i++)
    {
        if(p[i]) continue;
        int x=u[i],y=v[i];
        int w1=d1[x]+w[i]+dn[y];
        if(l[x]<=r[y]) inc[l[x]].push_back(w1),cut[r[y]].push_back(w1);
        int w2=d1[y]+w[i]+dn[x];
        if(l[y]<=r[x]) inc[l[y]].push_back(w2),cut[r[x]].push_back(w2);
    }
    s.insert(1e18);
    for(int i=1;i<=len;i++)
    {
        for(int x:inc[i]) s.insert(x);
        ans[i]=*s.begin();
        for(int x:cut[i]) s.erase(s.find(x));
    }
    while(q--)
    {
        int x,y;cin>>x>>y;
        if(p[x]) cout<<min(d1[n]+y-w[x],ans[p[x]])<<'\n';
        else cout<<min(d1[n],min(d1[u[x]]+y+dn[v[x]],dn[u[x]]+y+d1[v[x]]))<<'\n';
    }
}

A1124 T1 传奇特级超空间

我会 \(n\leq m\) !(大声)

找规律得:

\(f(n,m)=f(n-1,m)+f(n-1,m-1)\)

\(f(0,m)=1\)

\(f(n,1)=n+1\)

打表,对于 \(f(i,m)\) 找规律,可以发现是求:

\(\sum\limits_{0\leq i\leq n} \sum\limits_{0\leq j\leq m}\binom{i}{j}=(\sum\limits_{0\leq j\leq m+1} \binom{n}{i})-1\)

考虑求

\(\sum\limits_{0\leq i\leq m} \binom{n}{i} \bmod p\)

\(=\sum \binom{\lfloor \frac{n}{p} \rfloor}{\lfloor \frac{i}{p} \rfloor}\binom{n \bmod p}{i \bmod p} \bmod p\)

枚举余数:

\(\sum\limits_{0\leq i<p} \binom{n \bmod p}{i}\sum\limits_{0\leq j<\lfloor \frac{m}{p}\rfloor} \binom{\lfloor \frac{n}{p} \rfloor}{j} + \binom{\lfloor \frac{n}{p} \rfloor}{\lfloor \frac{m}{p} \rfloor}\sum\limits_{0\leq i\leq m \bmod p} \binom{n \bmod p}{i}\)

对于 \(\sum\limits_{0\leq j<\lfloor \frac{m}{p}\rfloor} \binom{\lfloor \frac{n}{p} \rfloor}{j}\),我们可以递归往下求。

复杂度 \(O(p\log_p n)\)

常数较大,使用 barrett 优化取模。

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

#define ull unsigned long long
#define ui128 __uint128_t
struct Barrett{
	ull d;ui128 m;
	void init(ull _d) {d=_d,m=(((ui128)(1)<<64)/d);}
	ull operator()(ull a)
    {
		ull w=(m*a)>>64;w=a-w*d;
		if(w>=d)w-=d;return w;
	}
}mod;

const int N=2e7+5;
int n,m,p,fac[N],inv[N];
int qpow(int a,int b) {int r=1;for(;b;b>>=1,a=a*a%p) if(b&1) r=r*a%p;return r;}
int C(int n,int m) {if(n<0||n<m) return 0;return mod(mod(fac[n]*inv[m])*inv[n-m]);}
int Lucas(int n,int m) {if(n<p&&m<p) return C(n,m);if(!m) return 1;return mod(Lucas(n/p,m/p)*C(mod(n),mod(m)));}

int f(int n,int m)
{
    if(m<0) return 0;
    int s1=0,s2=0,np=mod(n);
    for(int i=0;i<p;i++) s1=mod(s1+C(np,i));
    s1=mod(s1*f(n/p,m/p-1));
    for(int i=0;i<=mod(m);i++) s2=mod(s2+C(np,i));
    s2=mod(s2*Lucas(n/p,m/p));
    return mod(s1+s2);
}

signed main()
{
    cin>>n>>m>>p;mod.init(p);n++,m++;
    fac[0]=1;for(int i=1;i<p;i++) fac[i]=mod(fac[i-1]*i);
    inv[p-1]=qpow(fac[p-1],p-2);for(int i=p-2;~i;i--) inv[i]=mod(inv[i+1]*(i+1));
    cout<<f(n,m)-1<<'\n';
}

P7880 [Ynoi2006] rldcot

赛时想过同 Tourism 的回滚莫队+虚树做法,可惜常数太大只能过 50 pts。

考虑一个点 \(x\) 何时作为 lca 产生贡献。

可以是其本身,只需要询问区间包含了 \(x\) 这个点。

或者是来自 \(x\) 不同两棵子树的点对 \((a,b)\),钦定 \(a\leq b\)

但这样的点对太多,可以发现很多无用的点对。

对于点对 \((a,b),(c,d)\),若 \(a\leq c,d \leq b\),那么 \((a,b)\) 就是无用的,因为包含了 \((a,b)\) 一定包含了 \((c,d)\)

现在考虑计算 \(x\) 某棵子树 \(T_y\) 的贡献,那么对于 \(u \in T_y\),在集合 \(T_x-T_y\) 中找到 \(u\) 的前驱后继即可。即得到点对 \((pre_u,u),(u,suf_u)\)

这个可以用 dsu on tree 做,用 set 维护。

做完后,我们得到一些三元组 \((l,r,c)\) 表示只要包含了 \((l,r)\) 这个区间,那么就有一个 \(c\) 的深度。

先把 \(c\) 离散化,把询问和修改固定到左端点,从大到小扫描线,用树状数组维护前缀和。

对于每种深度,维护其最小的 \(r\)

复杂度 \(O(n\log^2 n+q\log q)\)

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

const int N=1e5+5,inf=1e9;
int n,m,V,c[N],sz[N],son[N],d[N],lsh[N],ans[N*5],lst[N];
struct node{int l,r,id;} q[N*5];
vector<pair<int,int>> G[N],inc[N];
set<int> s[N];
void upd(int x,int v) {for(;x<=n;x+=x&-x) c[x]+=v;}
int qsum(int x) {int r=0;for(;x;x^=x&-x) r+=c[x];return r;}

inline int rd()
{
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^48);
    return x*f;
}

void dfs(int u,int f)
{
    sz[u]=1;
    for(auto [v,w]:G[u])
    {
        if(v==f) continue;
        d[v]=d[u]+w;
        dfs(v,u);
        sz[u]+=sz[v];
        if(sz[v]>sz[son[u]]) son[u]=v;
    }
}

void calc(int u,int f,int rt)
{
    if(s[rt].size())
    {
        auto it=s[rt].lower_bound(u);
        int l=*prev(it),r=*it;
        if(l) inc[l].push_back({u,d[rt]});
        if(r!=inf) inc[u].push_back({r,d[rt]});
    }
    for(auto [v,_]:G[u]) if(v!=f) calc(v,u,rt);
}

void tadd(int u,int f,int rt)
{
    s[rt].insert(u);
    for(auto [v,_]:G[u]) if(v!=f) tadd(v,u,rt);
}

void solve(int u,int f,int kp)
{
    for(auto [v,_]:G[u]) if(v!=f&&v!=son[u]) solve(v,u,0);
    if(son[u]) solve(son[u],u,1);
    swap(s[u],s[son[u]]);
    s[u].insert(0),s[u].insert(inf);
    inc[u].push_back({u,d[u]});
    for(auto [v,_]:G[u]) if(v!=f&&v!=son[u]) calc(v,u,u),tadd(v,u,u);
    s[u].insert(u);
    if(!kp) s[u].clear();
}

signed main()
{
    n=rd(),m=rd();
    for(int i=1;i<n;i++)
    {
        int u=rd(),v=rd(),w=rd();
        G[u].push_back({v,w}),G[v].push_back({u,w});
    }
    dfs(1,0);
    for(int i=1;i<=n;i++) lsh[i]=d[i];
    sort(lsh+1,lsh+1+n),V=unique(lsh+1,lsh+1+n)-lsh-1;
    for(int i=1;i<=n;i++) d[i]=lower_bound(lsh+1,lsh+1+V,d[i])-lsh;
    solve(1,0,1);
    for(int i=1;i<=m;i++) q[i]={rd(),rd(),i};
    sort(q+1,q+1+m,[&](node a,node b){return a.l>b.l;});
    for(int i=1;i<=V;i++) lst[i]=n+1;
    for(int i=n,j=1;i>=1;i--)
    {
        for(auto [x,v]:inc[i]) if(x<lst[v]) upd(lst[v],-1),upd(lst[v]=x,1);
        while(j<=m&&q[j].l==i) ans[q[j].id]=qsum(q[j].r),j++;
    }
    for(int i=1;i<=m;i++) cout<<ans[i]<<'\n';
}

A1129 T1 分身

赛时想了个神逼乱搞,过了所有大样例。最后发现正确率在 70%,但是捆绑,所有只有 10pts,输麻了。

二分答案,设速度为 \(v\)

对于一个球 \(i\),一个分身能从一个 \(j\) 跑过来,\(t_j <t_i\),当且仅当 \(|x_i-x_j|\leq v\cdot t_i-v\cdot t_j\)

即:

\(x_j-v\times t_j \geq x_i-v\cdot t_i(x_i \geq x_j)\)

\(x_j+v\times t_j \leq x_i+v\cdot t_i(x_i < x_j)\)

可以发现若 \(x_i<x_j\) 时一定不满足一式,而 \(x_i \geq x_j\) 是一定不满足二式,所以可以一起考虑。

\(X_i=x_j-v\cdot t_j,Y_i=x_i+v\cdot t_i\)

那么条件转化为 \(X_j \geq X_i,Y_j \leq Y_i\)

若满足这个条件那么连边 \(j \rightarrow i\),问题转化为求最小路径覆盖,对于此题,就是求最小链覆盖。

根据 Dilworth 定理,最小链覆盖 = 最大反链,即找到一个最长的序列 \(a\),使得 \(X_{a-1}<X_{a},Y_{a-1}<Y_a\)

排序,扫描线或最长上升子序列均可。复杂度 \(O(c\times n\log n)\)\(c\) 为二分次数。

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

const int N=1e5+5;
int n,m,V,t[N],x[N],c[N];
double lsh[N];
void upd(int x,int v) {for(;x<=V;x+=x&-x) c[x]=max(c[x],v);}
int qmx(int x) {int r=0;for(;x;x^=x&-x) r=max(r,c[x]);return r;}
struct node{double x,y;} a[N];

int check(double v)
{
    for(int i=1;i<=n;i++) if(abs(x[i])>t[i]*v) return 0;
    for(int i=1;i<=n;i++) a[i].x=x[i]-v*t[i],lsh[i]=a[i].y=x[i]+v*t[i];
    sort(lsh+1,lsh+1+n);
    V=unique(lsh+1,lsh+1+n)-lsh-1;
    for(int i=1;i<=n;i++) a[i].y=lower_bound(lsh+1,lsh+1+V,a[i].y)-lsh;
    sort(a+1,a+1+n,[&](node a,node b){return a.x<b.x;});
    for(int i=1;i<=V;i++) c[i]=0;
    for(int i=1,j;i<=n;i=j+1)
    {
        j=i;
        while(j<n&&a[j+1].x==a[i].x) j++;
        int mx=0;
        for(int k=i;k<=j;k++) mx=max(mx,qmx(a[k].y));
        upd(a[i].y,mx+1);
    }
    return qmx(V)<=m;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;m++;
    for(int i=1;i<=n;i++) cin>>t[i]>>x[i];
    double L=0,R=2e6,res;
    for(int i=1;i<=50;i++)
    {
        double mid=(L+R)/2;
        if(check(mid)) res=mid,R=mid;
        else L=mid;
    }
    cout<<fixed<<setprecision(6)<<res<<'\n';
}

A1129 T3 序列

先考虑 75 pts 的 a[x]<=y 的部份分。

发现前缀最大值序列是单调不降的。

于是可以区间赋值 + 历史版本和做。

由于我不会线段树维护历史版本和,赛时想的是用珂朵莉树维护值相同的一段,然后改为区间加 + 历史版本和,用树状数组维护。

下面我们充分发扬这个做法。

现在前缀和数组不是单调不降的了,但可以发现,对于相同一个时间,序列是单调不降的。

先离线下来,对于序列每一个位置从左往右扫描线。那么相当于每次对一段时间的值区间取 max,也是单调不降的。(可以理解为把序列和时间的维度调换维护)

于是可以吉司机线段树 + 历史版本和。

我还是不会,但是我会乱搞,沿用上面的做法,当一段值相同再 upd,否则继续往下递归,还是用树状数组维护。

时限给了 8s,我觉得随便过。

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

const int N=1e6+5;
int n,m,t,a[N],b[N],ans[N],lst[N],v[N];
struct node{int l,r,v;};
vector<int> q[N];
vector<node> inc[N];
struct BIT{
    int c1[N],c2[N];
    void upd(int x,int v) {for(int V=x*v;x<=m/*focus*/;x+=x&-x) c1[x]+=v,c2[x]+=V;}
    int sum(int x) {int s1=0,s2=0;for(int y=x;y;y^=y&-y) s1+=c1[y],s2+=c2[y];return (x+1)*s1-s2;}
    int qsum(int l,int r) {return sum(r)-sum(l-1);}
}tr1,tr2;

void upd(int l,int r,int v)
{
    tr1.upd(l,v),tr1.upd(r+1,-v);
    tr2.upd(l,v*t),tr2.upd(r+1,-v*t);
}

#define lc (k<<1)
#define rc (k<<1|1)
#define mid (l+r>>1)
int mn[N*4],mx[N*4],tag[N*4];
void pushdown(int k)
{
    if(!tag[k]) return;
    mn[lc]=mn[rc]=mx[lc]=mx[rc]=tag[lc]=tag[rc]=tag[k];
    tag[k]=0;
}
void tupd(int x,int y,int v,int k=1,int l=1,int r=m/*focus*/)
{
    if(v<=mn[k]) return;
    if(l>=x&&r<=y&&mn[k]==mx[k])
    {
        upd(l,r,v-mn[k]);
        mn[k]=mx[k]=tag[k]=v;
        return;
    }
    pushdown(k);
    if(x<=mid) tupd(x,y,v,lc,l,mid);
    if(mid<y) tupd(x,y,v,rc,mid+1,r);
    mn[k]=min(mn[lc],mn[rc]);
    mx[k]=max(mx[lc],mx[rc]);
}

char buf[1<<15],*p1=buf,*p2=buf;
#define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<15,stdin),p1==p2)?-1:*p1++)
inline int rd()
{
    int x=0;char c=nc();
    for(;!isdigit(c);c=nc());
    for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48);
    return x;
}

signed main()
{
    n=rd(),m=rd();
    for(int i=1;i<=m;i++)
    {
        int x=rd(),y=rd();
        if(lst[x]) inc[x].push_back({lst[x],i-1,v[x]});
        lst[x]=i,v[x]=y;
        q[x].push_back(i);
    }
    for(int i=1;i<=n;i++) if(lst[i]) inc[i].push_back({lst[i],m,v[i]});
    for(t=1;t<=n;t++)
    {
        for(auto [l,r,v]:inc[t]) tupd(l,r,v);
        for(int i:q[t]) ans[i]=tr1.qsum(1,i)*(t+1)-tr2.qsum(1,i);
    }
    for(int i=1;i<=m;i++) cout<<ans[i]<<'\n';
}
posted @ 2023-11-01 15:33  spider_oyster  阅读(32)  评论(0编辑  收藏  举报