Loading

每日总结 (一)

8.31

P5665划分 :

算法:贪心,单调队列

首先是一个数学知识 \(a^2+b^2\le(a+b)^2\) 所以我们应该尽可能地多分段,当然在保证分成的段单调递增的前提下,也应该尽可能地把数分给小的那一段,然后开 __int128 多取模。

注意 : 取模时应该注意得数的正负如果是负的应该先加 \(mod\) 然后再模 \(mod\). 提前计算好运算的上界,决定是开\(int\)还是开\(long long\) 还是 __int128(__int128 读入只能用快读,输出只能用快写)。


P5021赛道修建

算法:二分答案,贪心,求LCA

贪心思路:找一个 \(a_i\) 找到第一个不小于 \(l-a_i\) 的数 \(a_j\) 去除这两个数,记录答案,若没找到则跳 \(a_i\) 的后继

证明:

1.最优匹配中包含 \(a_x\) : 则应该选择 \(a_y\),因为选择小于它的元素都不满足条件。选择大于它的元素的情况,选择 \(a_y\) 对其是具有决
策包容性的。

2.最优匹配中不包含 \(a_x\) : 则最优排列中必然包含 \(a_y\),否则还有未统计的匹配对 \((a_x,a_y)\)。设 \(a_y\)\(a_z\) 匹配,显然 \(a_z≥a_x\) 。那么这个匹配可能不是最优,或与选 \(a_x\) 等效。


P7078贪吃蛇 :

算法:贪心

结论:只需模拟两个阶段:

一:所有最强蛇进食后都不是最弱蛇,放心大胆吃!

二:所有最强蛇进食后都是最弱蛇,直到有条蛇可以放心吃为止(吃了后不是最弱或者只剩两条)

注意:不需要开 \(long long\) 的题不要盲目开,注意常数优化。



9.1

P8632居民集会

算法: 模拟退火,斜率优化DP

\(DP[i][j]\) 表示在第 \(i\) 个居民设置第 \(j\) 个集会点,前 \(i\) 个居民的最小值, \(st\)\(t\)的前缀和数组,\(std\)\(t*d\) 的前缀和数组,转移DP式子就是:

\[dp_{i,j}=min_{k=0}^{i-1} (dp_{k,j-1}+d_i*(st_i-st_k)-(std_i-std_k)) \]

然后套个斜率优化
注意:斜率优化的实现形式。


P7116微信步数

算法:动态规划DP 拉格朗日插值

处理出来横竖的延伸长度,先处理两轮的,然后再在这两轮上扩展。

注意 : 取模时应该注意得数的正负如果是负的应该先加 \(mod\) 然后再模 \(mod\). 提前计算好运算的上界,决定是开\(int\)还是开\(long long\) 还是 __int128(__int128 读入只能用快读,输出只能用快输),多取模。



9.2:

b

算法:数学知识的应用,\(k\) 优解

首先看到要比较两个特别大的组合数,我们可以在上面套个 \(log\) 然后组合数的大小就变的可以接受了,可以拿 \(long double\) 存下来。

然后就是 \(k\) 优解问题打个表观察发现,把组合数写成三角的形式,他是一个由两边向四周的辐射状,然后拿优先队列维护即可。


c

算法:动态规划DP

设置 \(f_{i,j,0/1}\) 表示长为 \(i\) 的序列,在后面加 \(j\) 种数就会变合法,\(0/1\) 表示当前状态是否合法。

状态转移式子为:

\[dp[i+1][j][1]+=dp[i][j][1]*j \]

\[dp[i+1][j+1][0]+=dp[i][j][1]*(m-j) \]

\[dp[i+1][j][0]+=dp[i][j][0]*(m-j) \]

\[dp[i+1][j][1]+=dp[i][j][0]*j \]


d

算法:主席树

考虑对一个人 \(i\) 有贡献的事件 \(k\)\(s_k-s_i\) 的一个上升序列。

即一个 \(s_i\) 的上升序列,对于每一个点指向后面第一个比它大的数,形如一棵树,它到路径上的点即为贡献事件集。

求两点 \(lca\) 即可得到对两点均有贡献的事件,最后组主席树统计答案即可。



9.3

点击查看

今天是抗战胜利80周年。上午看了阅兵


P4001 狼抓兔子

算法:图论建模(平面图转对偶图), 最短路

观察题面,题面给了一个网格图,还带边权,显然是最小割,可以转成对偶图然后跑最短路,跑完之后就是最小割,具体的:给每个格子编号,确保网格图中的边每条都恰好被一条对偶图中的边切,对偶图中的边权就是所切的网格图中的边的边权

注意:看清对偶图中的终点的编号是什么!!!


P1850 换教室

算法:期望DP

\(DP_{i,j,1/0}\) 表示前 \(i\) 间教室换了 \(j\) 间,第 \(i\) 间教室 (是/否) 换了。

转移式子:

\[dp_{i,j,0}=min\left\{\begin{matrix} dp_{i-1,j,0}+dis_{c[i-1],c[i]} \\ dp_{i-1,j,1}+(1-k_i)\times dis_{c[i-1],c[i]} + k_i \times dis_{c[i-1],d[i]} \end{matrix}\right. \]




\[dp_{i,j,1}=min\left\{\begin{matrix} dp_{i-1,j-1,0}+(1-k_i)\times dis_{c[i-1],c[i]} + k_i \times dis_{c[i-1],d[i]} \\ \\ dp_{i-1,j-1,1}+\\ (1-k[i-1])\times (1-k[i])\times dis[c[i-1]][c[i]]+\\ (1-k_{i-1})\times k_i\times dis_{c[i-1],d[i]}+\\ k[i-1]\times (1-k[i])\times dis_{d[i-1],c[i]}+\\ k[i-1]\times k[i]\times dis_{d[i-1],d[i]} \end{matrix}\right. \]

注意\(Floyd\) 要先枚举中转点 \(k\)


P3960 列队

算法:线段树,平衡树,树状数组,块状链表。

这道题可以用 线段树/平衡树/树状数组 来写,我写的动态开点线段树。

首先观察题面,一个人 \((x,y)\) 出队再回队,就是把 \(x\) 这一行的第 \(y\) 个人删了全体左移,然后把最后一列第 \(x\) 个人删了,整体上移,然后把 \((x,y)\) 这个人放在最后一列最后一个。

我们只需要对每一行和最后一列分别维护一颗线段树,动态维护就可以了,但是我们一算这样的空间复杂度是 \(O(n^2 log_2n)\) 的。

显然需要优化,我们发现没有出过队的人没什么用,我们就只对出过队的人建立与他相关的节点,并记录它前面删过的点的个数,直接统计即可

注意:对于题面的转化,和知识的应用。



9.4

P4138 挂饰

算法:动态规划DP,背包DP

\(dp[i][j]\) 表示前 \(i\) 个物品,还剩 \(j\) 个挂钩,可以获得的最大价值,

状态转移方程:

$ if(j>=pos[i].a)$

\[dp[i][j]=max(dp[i-1][j],dp[i-1][j-pos[i].a+1]+pos[i].b) \]

$ else $

\[dp[i][j]=max(dp[i-1][j],dp[i-1][1]+pos[i].b) \]

答案为 $$min_{i=0}^{n} dp[n][i]$$

注意 :在转移之前应该先排序,确保有挂钩最多的先遍历


P5023 填数游戏

算法:打表,动态规划DP

看到 \(n\) 的范围很小,然后和出题人的脑电波同频共振,然后再被宇宙射线击中一下,再打个表.

寻找一下规律发现:\(a_{i,j}=a_{i,i+1} \times 3^{j-i-1}\)\(n\) 很小可以打个有剪枝的暴搜,让它辛苦一下,跑一个 \(8\times 9\) 的表然后 \(O(nm)\) 预处理,\(O(1)\) 查询。

注意:数组很大时不能直接在定义时拿 { } 定义编译时 \(C++\) 会自动补全,本地运行没事,测评时会 \(CE\).


P4751 动态DP

算法:动态DP,树链剖分,线段树

动态DP模板题,首先暴力 \(DP\) :设 \(DP_{i,0/1}\) 表示到 \(i\) 号节点是否选它,转移式子也很显然,然后把轻重儿子分开考虑设 \(g_{i,0/1}\) 表示 \(i\) 号节点所有轻儿子所能构成的最大权独立集。

转移方程:

\[f_{i,0}=g_{i,0}+max(f_{j,0},f_{j,1}) \]

\[f_{i,1}=g_{i,1}+a_i+f_{j,0} \]

然后构建一个转移矩阵​

\[\begin{bmatrix} g_{i,0} & g_{i,0} \\ g_{i,1} & -\infty \end{bmatrix} \times \begin{bmatrix} f_{j,0} \\ f_{j,1} \end{bmatrix} = \begin{bmatrix} f_{i,0} \\ f_{i,1} \end{bmatrix} \]

最后发现更改一个点的权值只会改这条链的链头的父亲,直接 就是 一个 线段树维护区间。

注意:数组大小要开够


P5024 保卫王国

算法:动态DP

先推个式子,和模板题长得很像,然后搓个矩阵,强制选或不选只需要在转移矩阵的对应位置赋成 \(inf\) 确保是极大值只能选另一个即可

剩下的就和板子题一样了。



9.5

P10237 Latent Kindom

算法:线段树,二分
看到值域 \(1e18\) 先离散化,然后对每个序列建动态开点线段树,并维护他们的大小。

查询答案的时候两棵树一起跑,然后在线段树上二分即可。

注意:看清没个变量对应的是什么,注意 \(+1 / -1\) 的细节处理。


P5666 树的重心

算法:树形 \(DP\),树状数组

首先对于删去一条边后的一棵子树的大小 \(S\) 应该满足:

\[n-2 * s_x\le S\le n-2 * g_x \]

其中 \(g_x\)\(x\) 的儿子中子树最大的大小。

然后分别用两个树状数组维护 \(S\) ,然后发现最后就是一个区间求和。

注意:清空一定要清全


P7963 棋局

算法:并查集,线段树

无需多言,请吧:

点击查看


#include<bits/stdc++.h>
#define int long long
const int N = 2e5+10, M = 8e6+10;
using namespace std;
inline int read() {
	int x=0,fa=1;char ch=getchar();
	while(ch<'0'||ch>'9') fa=ch=='-'?-1:1,ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return x*fa;
}
int T_T,n,m,Q,val[N],e[4][N],ans[N]; string s;
int dx[4]= {-1,0,1,0},dy[4]= {0,-1,0,1};
struct edge { int col,lv,x,y,id;}a[N];
inline bool cmp1(edge a,edge b) {return (a.lv==b.lv)?(a.id<b.id):(a.lv<b.lv);}
inline bool cmp2(edge a,edge b) {return a.id<b.id;}
inline int pos1(int x,int y) {return (x-1)*m+y;}
inline int pos2(int x,int y) {return (y-1)*n+x;}
pair<int,int> Get(int pos) {return make_pair((pos-1)/m+1,(pos-1)%m+1);} 
struct Tree1 {
	#define mid ((l+r)>>1)
    struct node {int ls,rs,sz;}tr[M]; int rt[N],cnt;
    int ls(int x) {return tr[x].ls;} int rs(int x) {return tr[x].rs;}
    inline void init() {memset(rt,0,sizeof rt);memset(tr,0,sizeof(tr));cnt=0;}
    inline void push_up(int i) {tr[i].sz=tr[ls(i)].sz+tr[rs(i)].sz;}
    inline int update(int i,int l,int r,int x,int opt=1) {
        i=!i?++cnt:i;
        if(l==r) return opt==-1?0:(tr[i].sz+=(!tr[i].sz),i);
        if(x<=mid) tr[i].ls=update(ls(i),l,mid,x,opt);
        else tr[i].rs=update(rs(i),mid+1,r,x,opt);
        return push_up(i),(tr[i].sz)?i:0;
    }
    inline int merge(int x,int y,int l,int r) {
        if(x==0||y==0) return x|y;
        if(l==r) return tr[x].sz=min(tr[x].sz+tr[y].sz,1ll),x;
        tr[x].ls=merge(ls(x),ls(y),l,mid);
        tr[x].rs=merge(rs(x),rs(y),mid+1,r);
        return push_up(x),x;
    }
    inline int query(int i,int l,int r,int x) {
        if(i==0||x==0) return 0; if(l==r) return tr[i].sz;
        if(x<=mid) return query(ls(i),l,mid,x);
        else return tr[ls(i)].sz+query(rs(i),mid+1,r,x);
    }
    inline int ask(int i,int l,int r,int x) {
        if(i==0||x==0) return 0;
        if(l==r) return tr[i].sz;
        if(x<=mid) return ask(ls(i),l,mid,x);
        else return ask(rs(i),mid+1,r,x);
    }
    inline int ask_num(int i,int l,int r) {return query(rt[i],1,n*m,r)-query(rt[i],1,n*m,l-1);}
    #undef mid
}T[4];
inline void Merge(int x,int y) {
    T[0].rt[x]=T[0].merge(T[0].rt[x],T[0].rt[y],1,Q);
    T[1].rt[x]=T[1].merge(T[1].rt[x],T[1].rt[y],1,Q);
    T[2].rt[x]=T[2].merge(T[2].rt[x],T[2].rt[y],1,n*m);
    T[3].rt[x]=T[3].merge(T[3].rt[x],T[3].rt[y],1,n*m);
}
struct Tree2 {
    int fa[N],sz[N],ma[N],mi[N];
	inline void init(int p) {for(int i=0;i<=p;i++) fa[i]=ma[i]=mi[i]=i,sz[i]=1;}
    inline int find(int x) {return (fa[x]==x)?x:find(fa[x]);}
    inline void merge(int x,int y,int opt=0) {
        x=find(x),y=find(y); if(x==y) return;
        if(sz[x]<=sz[y]) swap(x,y);
        sz[x]+=sz[y]; fa[y]=x;
        ma[x]=max(ma[x],ma[y]);
        mi[x]=min(mi[x],mi[y]);
        if(opt) Merge(x,y);
    }
}Tr[3];
inline bool check(int x,int y) {return (y==0||y>=x)?0:(a[x].col!=a[y].col&&a[x].lv>=a[y].lv);}
inline void init() {
    memset(e,0,sizeof(e)); memset(val,0,sizeof(val)); memset(ans,0,sizeof(ans));
    Tr[0].init(n*m); Tr[1].init(n*m); Tr[2].init(n*m);
    T[0].init();T[1].init();T[2].init();T[3].init();
}
inline void solve() {
	n=read();m=read();Q=read(); init();
    for(int i=1;i<=n;i++) {
    	cin>>s; s=' '+s;
        for(int j=1;j<m;j++) e[1][pos1(i,j+1)]=e[3][pos1(i,j)]=s[j]-'0';
    }
    for(int i=1;i<n;i++) {
		cin>>s; s=' '+s;
		for(int j=1;j<=m;j++) e[0][pos1(i+1,j)]=e[2][pos1(i,j)]=s[j]-'0';
    }
    for(int i=1;i<=Q;i++) a[i]={read(),read(),read(),read(),i};
    sort(a+1,a+Q+1,cmp1);
    for(int i=1;i<=Q;i++) a[i].lv=i;
    sort(a+1,a+Q+1,cmp2);
    for(int i=1;i<=Q;i++) val[pos1(a[i].x,a[i].y)]=i;
    for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			int id=pos1(i,j);
            for(int k=0;k<4;++k) {
                if(e[k][id]>1) {
                    int nxt=pos1(i+dx[k],j+dy[k]);
                    if(!val[id]&&!val[nxt])
                        Tr[(e[k][id]==3)?0:(k%2+1)].merge(id,nxt);
                }
            }
        }
    }
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            int id=pos1(i,j),Id=Tr[0].find(id);
            T[2].rt[Id]=T[2].update(T[2].rt[Id],1,n*m,pos2(i,j));
            T[3].rt[Id]=T[3].update(T[3].rt[Id],1,n*m,id);
            for(int k=0;k<4;++k) {
                if(e[k][id]==3) {
                    int nxt=pos1(i+dx[k],j+dy[k]);
                    if(val[nxt]) {
                        int col=a[val[nxt]].col;
                        T[col].rt[Id]=T[col].update(T[col].rt[Id],1,Q,a[val[nxt]].lv);
                    }
                }
            }
        }
    }
    for(int i=Q;i>=1;--i) {
        int id=pos1(a[i].x,a[i].y),col=a[i].col;
        for(int j=0;j<4;j++) {
        	if(e[j][id]==3) {
                int nxt=pos1(a[i].x+dx[j],a[i].y+dy[j]),Fnxt=Tr[0].find(nxt);
                T[col].rt[Fnxt]=T[col].update(T[col].rt[Fnxt],1,Q,a[i].lv,-1);
            }
        }
        for(int j=0;j<4;j++) {
            if(e[j][id]==3) {
                int nxt=pos1(a[i].x+dx[j],a[i].y+dy[j]);
                if(val[nxt]&&val[nxt]<i) continue;
                Tr[0].merge(id,nxt,1);
            }
        }
        int Id=Tr[0].find(id);
        ans[i]=Tr[0].sz[Tr[0].find(id)]+T[1^col].query(T[1^col].rt[Id],1,Q,a[i].lv);
        for(int j=0;j<4;j++) {
            if(e[j][id]==2) {
                int nxt=pos1(a[i].x+dx[j],a[i].y+dy[j]);
                if(val[nxt]&&val[nxt]<i) continue;
                Tr[j%2+1].merge(id,nxt);
            }
        }
        int mx1=Tr[1].ma[Tr[1].find(id)],mx2=Tr[2].ma[Tr[2].find(id)],mn1=Tr[1].mi[Tr[1].find(id)],mn2=Tr[2].mi[Tr[2].find(id)];
        ans[i]+=mx2-mn2+1+(mx1-mn1)/m+1;
        int dmx=pos2(Get(mx1).first,Get(mx1).second),dmn=pos2(Get(mn1).first,Get(mn1).second);
        ans[i]-=T[2].ask_num(Id,dmn,dmx)+T[3].ask_num(Id,mn2,mx2);
        if(e[0][mn1]==2&&check(i,val[mn1-m])) ans[i]+=(!T[1^col].ask(T[1^col].rt[Id],1,Q,a[val[mn1-m]].lv));
        if(e[1][mn2]==2&&check(i,val[mn2-1])) ans[i]+=(!T[1^col].ask(T[1^col].rt[Id],1,Q,a[val[mn2-1]].lv));
        if(e[2][mx1]==2&&check(i,val[mx1+m])) ans[i]+=(!T[1^col].ask(T[1^col].rt[Id],1,Q,a[val[mx1+m]].lv));
        if(e[3][mx2]==2&&check(i,val[mx2+1])) ans[i]+=(!T[1^col].ask(T[1^col].rt[Id],1,Q,a[val[mx2+1]].lv));
        for(int j=0;j<4;j++) {
            if(e[j][id]==1) {
                int nxt=pos1(a[i].x+dx[j],a[i].y+dy[j]);
                if(val[nxt]&&val[nxt]<i) ans[i]+=(check(i,val[nxt])&&(T[1^col].ask(T[1^col].rt[Id],1,Q,a[val[nxt]].lv)==0));
                else if(Tr[0].find(id)!=Tr[0].find(nxt)) ans[i]++;
            }
        } ans[i]--;
    }
    for(int i=1;i<=Q;i++) printf("%lld\n",ans[i]);
}
signed main() {T_T=read();while(T_T--) solve();return 0;}





9.6

A P3760

看到异或运算,考虑按位考虑贡献,用前缀和优化,化一下式子。

\[\sum_{k=0}^{maxbit} 2^k \sum_{l=0}^{n}\sum_{r=l+1}^{n} ((sum_r-sum_l)\mod 2) \]

考虑减法的按位意义,发现与借位有关,分 0/1 的情况讨论,用树状数组维护即可。


C P5443

没有修改操作时是 kruscal 重构树裸题,

有了修改操作不是很好维护,考虑根号算法,根号重构,对每 \(sqrt{n}\) 个询问进行一次暴力重构,

将边按权值排序后,将无修改的边加入边集,修改的边将最新的权值加入,用可撤销并查集维护这个过程。



9.7

P3730 曼哈顿交易

算法:莫队,分块

看到题面是询问区间计数的相关,又没什么思路,线段树也不好维护,考虑莫队,

用分块维护相关信息,每次移动左右指针时,更新对应值,然后查询时用分块查询即可。


P10592 isn

算法:树状数组,组合计数,动态规划DP

\(dp_{i,j}\) 表示前 \(i\) 个数选 \(j\) 个并且选第 \(i\) 个数构成非降序列。 转移方程:

\[ dp_{i,j}=\left\{\begin{matrix} 1 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ j=1\\ \sum_{k=1}^{i-1} [a_k\le a_i] dp_{k,j-1} \ \ \ \ \ j \ne 1 \end{matrix}\right. \]

用树状数组实现转移,然后 \(mp_i=\sum_{j=1}^{n} dp_{j,i}\)

用容斥知识 \(ans=\sum_{i=1}^{n} mp_i \times (n-i)! - mp_{i+1} \times (n-i-1)!\times(i+1)\)

P14005 棋盘游戏

算法:构造?

神仙结论题,输出什么都能A(?

可以证明无论初始黑点是什么,都可以通过一系列操作构造任意的形态。



9.8

P12685 排队 加强版

算法:树套树

树套树板子题,逆序对经典 \(Trick\) 两个数交换只有两个数中间的数会影响答案。

只需要初始算一遍,然后每次计算贡献的差值即可。


P10235 舞萌基本练习

算法:二分答案,树状数组

经过简单手玩,发现答案显然具有单调性,遂二分答案

然后用树状数组维护逆序对即可。

注意:清空能不暴力清,就不暴力清,顺着来路走回去也许会更好。



9.9

P10833 下 Niz

算法:分治

我们根据最值分治,最值确定了,区间长度也就确定了,设 \(las\) 为这个数上一次出现的位置,显然当一个区间合法时 \(las_l~las_r\) 都不能在当前区间出现。

显然对于 \(l\) 我们只需要判断 \(las\) 的最大值即可,用两个 \(ST\) 表分别维护原数组和 \(las\) 数组的 \(RMQ\) 就行了。

然后正常分治即可。


P7482 不条理狂诗曲

算法:分治,动态规划DP

首先最值分治,设:\(f_{l,r}\) 表示区间的最大值

\(fl_{i,0/1}\) 表示:\([i,mid]\) 不选/选 \(a_{mid}\) 的最大值,\(fr\) 同理,转移方程:

\[f_{l,r} =max(fl_{l,0}+fr_{r,1},fl_{l,1}+fr_{r,0},fl_{l,0}+fr_{r,0}) \]

\[=max(fl_{l,0}+fr_{r,1},max(fl_{l,1},fl_{l,0})+fr_{r,0})) \]

\(mp\)\(fl_{i,0}\) 的前缀和

\(ms\)\(max(fl_{l,1},fl_{l,0})+fr_{r,0})\) 的前缀和

那么对答案的贡献就为\(mp_{k-1}+(k-l) \times fr_i\)\(ms_{mid} - ms_{k-1}+(mid-k+1)\times fr_{i,0}\)

posted @ 2025-09-02 16:09  dfgz  阅读(19)  评论(1)    收藏  举报