一月
长路辗转离合悲欢 人聚又人散
2025.1.02
A.未知
优点:
开场十分钟看出是二分图匹配
缺点:
没注意到特殊值的总长度为 \(O(n)\),算法的复杂度记错了,线段树优化建图写得不熟练。
注意:
线段树优化建图后 Dinic 跑最大流求二分图最大匹配能想到。
但是面对一个值必须在特定区间出现和一个位置只能放一定范围的值这两个限制,没有注意到所有 限定值必须在特定区间出现 的限定区间总长度为 \(O(n)\) 级别。
注意到这个之后对于被限定的值和区间直接暴力连边,没被限定的值线段树优化建图即可。
B.观光缆车
优点:无
缺点:在草稿纸上手玩不走脑子,不严谨。
知识:杨辉三角日字对角线和是斐波那契。
注意:
正常手玩组合数向上拆分后会发现是斐波那契,注意到这个之后能拿 80 分。
能想到对于一个正方形把它拆成 \(O(n)\) 个本质不同路径,但是没有换种角度竖着看就是组合数的列求和。
C.旅游
优点:无
缺点:思考没有逻辑性,大脑一片混乱
注意:
遇到博弈等要自己想出最优决策的题要冷静下来分析,通常有些性质是比较容易想出来的,一分不拿直接弃肯定是会拉开很大差距的,思考问题要有逻辑,不要浪费时间在漫无目的地发呆上面。
\(A\le B\) 答案就是 1,这个没想到不应该。
最小最大博弈通过二分答案转换成 01 问题这个要记牢。
树上问题要考虑树形 dp 。
2025.1.03
A.钓鱼
优点:基本想出来了
缺点:最后没想到上线段树,认为不可维护。
注意:
要清楚自己要实现什么东西,这个考虑清楚了就很容易想到是可以使用什么东西维护的。
碰到二分图匹配要及时想到 Hall 定理。
B.挖野菜
优点:无
缺点:字符串加训!!!
知识:
到根路径并大小即为虚树大小,等于按 dfs 序排序后相邻两点距离和除以 2,或到根路径总长度减去按 dfs 序排序后相邻两点 LCA 到根路径的长度和。
经典问题:
求有序二元正整数对 \((x, y)\) 的数量,满足 \(1\leq x, y\leq n\),且 \(s\) 的长度为 \(x\) 的前缀和长度为 \(y\) 的后缀拼接得到的字符串是 \(t\) 的子串。
注意:
字符串问题通常都是分析一下如何求答案后疯狂数据结构。
例如这题既可以正反串分别建出失配树之后在正串失配树上线段树合并,也可以反串建 SAM 自上而下线段树合并求出每个点可以匹配哪几个后缀后再在正串上自下而上线段树合并。
C.单独行动
优点:无
缺点:碰到图论计数题没有头绪。
注意:通过 n<=20 很显然能猜出是状压计数,但是我们要对计数的东西的构建过程有个概念,设 \(f_i\) 为状态 \(i\) 中的点的导出子图为 DAG 的方案数,初始 0/单个点 一定的 DAG,关键是之后我们要向已有的 DAG 上新添加一批入度为 0 的点,知道 DAG 的构建过程之后我们就能保证一直都是 DAG,就可以 DP 了,然后肯定要猜个容斥系数。
2025.1.04
[ZJOI2019] 线段树
神仙题,没有头绪,建议多看。
注意:
暴力复杂度指数级但正解要求多项式复杂度的题一定要找性质,例如这题就是将区间覆盖造成的影响的节点分为 5 种之后 dp。
2025.1.05
A.简单的递归
优点:开场没半个小时就会了
缺点:数组没改哈希表
注意:
\(\varphi(n)\) 其实就是把 n 唯一分解后对于每个质因数 \(\times\frac{p_i-1}{p_i}\)。也就是说 n 本身的质因数消完之后后面质因数就不再变化,指数就是加等差数列,暴力跑完 \(2log_n\) 次之后直接做。
B: 简单的线性代数
优点:会打行列式求值板子
缺点:根本没想矩阵树定理。
注意:
行列式是可以二维差分的,通过初等行列变换可证。
对在主对角线上的正方形进行矩阵加换成二维差分就等同于矩阵树定理求生成树个数中添加了一条 \((l,r+1)\) 的边,后面把非树边影响的点拿出来建虚树求矩阵树定理求生成树个数即可。
C:简单的字符串
优点:会打 SAM
缺点:不会
注意:
不是人能改的。
2025.1.06
A: 随机游走
优点:无
缺点:只记得自己在梦熊见过这题,具体做法一丁点印象都没有,事实证明改过去了不代表会了。
Trick:
一个集合,\(m\) 次操作,每次操作为每个点变为集合中某个特定子集的和,可以按长度为 \(\log n\) 分为 \(\frac{n}{\log n}\) 块,每块 \(2^{\log n}\) 预处理,可将时间复杂度由 \(O(nm)\) 变为 \(O(\frac{nm}{\log n})\)。
注意:
行向量乘矩阵求单点值是 \(O(n)\) 的。下辈子注意。
B: 钥匙问题
优点:无
缺点:碰到暴力都比较难写的题好像一分都拿不到。
C: 最长公共子序列
优点:无
缺点:按段设 dp 是很容易想到的,没继续往后面想单调性不应该。
2025.1.08
A: 罗盘
优点:会
缺点:做的太慢,应该开局就打表的。
B: 树上睡觉
优点:想了很多,长剖,分三种情况,枚举 lca 什么基本都想到了。
缺点:想的很乱。最大的败笔是对于不能跳过链顶这个限制选择直接变成区间求 \(\min\) 而不是将 b 排完序之后预处理出来数组,场上头脑不足够清醒,30 分纯暴力也没打出来。
C: IMAWANOKIWA
缺点:之前做过的题结论直接放过来能拿 20 分,但是之前那道题没改。
[USACO24DEC] It's Mooin' Time P
优点:很快猜到凸性了
缺点:不会 \((max,+)\) 卷积优化 \(dp\)。
注意:
对于有凸性的函数可以 \((max,+)\) 卷积优化,凸性表现为差分数组单调,所以把差分数组进行归并排序即可
2025.1.09
A.猪国杀 CF1495E
优点:很快知道一定有一方要出完
缺点:短暂思考后就认为这题一定是 \(O(n\log n)\) 恶心模拟,并没有深度思考,感觉这个题正解靠感性猜想的话和 NOIPT1 差不多
注意:
碰到两方轮流的题要思考一下顺序是否是必须的,轮与轮之间真的有界限吗?
B: 基础想象练习题
缺点:凡是需要容斥及推式子的题一点思路都没有。
C: 死亡搁浅 P5360 [SDOI2019] 世界地图
缺点:发现七十分是送的之后突然意识到自己不会打 LCT 板子了,因为之前是死记没有理解的所以场上根本写不出来。
注意:
发掘题目性质还是非常重要的,例如本题每添加一列形成的环一定是和上一列的点形成的,于是保留关键点的虚树就能快速做到前缀和后缀的合并。
2025.1.10
A.序列 CF1787I
缺点:很容易发现的限制没有用一整场都没发现。
经典问题:
求序列所有子区间的最大子段和
分治处理,考虑跨过分治中心的区间 $[l,r] $的最大子段和,记 \(lans\) 为 \([l,mid]\) 的最大子段和,\(rans\) 为 \([mid+1,r]\) 的最大子段和,\(lmx\) 为 \([l,mid]\) 的最大后缀和,\(rmx\) 为 \([mid+1,r]\) 的最大前缀和,那么区间 $[l,r] $ 的最大前缀和为 \(\max(lans,rans,lmx+rmx)\) 。
分别考虑选哪个来做贡献之后可以二维数点,时间复杂度 \(O(n\log^2 n)\)。
比较优的做法是固定左端点,那么分治中心右侧会被分成三段,第一段是取 \(lans\),第二段是取 \(lmx+rmx\),第三段是取 \(rans\),而随着左端点向左移动,右侧三段的两个分界点是单调右移的,可以用两个指针维护,时间复杂度 \(O(n\log n)\)。
B.关键点 Two Avenues
nan 鲜花:
暴力写起来一百多行只能拿 \(20\) 分,不过只要爱拼,暴力和性质分加起来只需要 10KB 就能获得 80 分。
点击查看代码
#include <bits/stdc++.h>
#define Un unsigned
#define LL long long
#define __ __int128
#define DB double
#define LD long double
#define pii pair<int,int>
#define pLi pair<LL,int>
#define pLL pair<LL,LL>
#define fr first
#define se second
#define pb push_back
#define Ve vector<int>
#define VE vector<LL>
#define Vpii vector<pii>
using namespace std;
inline int read()
{
int x = 0,f = 1;char ch = getchar();
while (!isdigit(ch)) (ch == '-') && (f = -1),ch = getchar();
while (isdigit(ch)) x = x*10+ch-48,ch = getchar();
return x*f;
}
const int N = 2e5+5;
struct Edge{int u,v;}E[N];
struct edge{int to,pre;}e[N<<1];
int las[N],cnt,a[N],b[N],n,m,k,id[N];
void add(int u,int v){e[++cnt] = {v,las[u]},las[u] = cnt;}
namespace SOL1//前人栽树
{
int h[N],e[N],del[N],ne[N],idx,Th[N],Te[N],Tid[N],Tne[N],Tidx,dfn[N],low[N],R[N],timestamp,stk[N],top,id[N],dcc_cnt,ka[N],kb[N],ls[N],rs[N],ids[N],cnt[N],Q,pe[N],st[18][N],loggg[N],Tdfn[N],Ttimestamp,ss[N];
vector<int> vl[N],vr[N],opl[N],opr[N];
int tr[N];
LL get_hsh(int a,int b)
{
if(a>b) swap(a,b);
return a*1000000LL+b;
}
void add(int a,int b){
e[idx]=b,del[idx]=0,ne[idx]=h[a],h[a]=idx++;
e[idx]=a,del[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
void Tadd(int a,int b,int i)
{
Te[Tidx]=b,Tid[Tidx]=i,Tne[Tidx]=Th[a],Th[a]=Tidx++;
Te[Tidx]=a,Tid[Tidx]=i,Tne[Tidx]=Th[b],Th[b]=Tidx++;
}
void tarjan(int u,int p=0){
dfn[u]=low[u]=++timestamp;
stk[++top]=u;
for(int i=h[u];i;i=ne[i]) if(!del[i]){
int v=e[i];
if(v==p) continue;
if(!dfn[v]){
tarjan(v,u),low[u]=min(low[u],low[v]);
if(dfn[u]<low[v]){
Q++,ls[Q]=dfn[v],rs[Q]=R[v],ids[Q]=i>>1;
}
}
else low[u]=min(low[u],dfn[v]);
}
R[u]=timestamp;
if(low[u]==dfn[u]){
int y;
dcc_cnt++;
do{
y=stk[top--];
id[y]=dcc_cnt;
}while(y^u);
}
}
int get(int a,int b){
return Tdfn[a]<Tdfn[b]?a:b;
}
void dfs1(int u,int p){
st[0][Tdfn[u]=++Ttimestamp]=p;
for(int i=Th[u];i;i=Tne[i]){
int v=Te[i];
if(v==p) continue;
pe[v]=Tid[i];
dfs1(v,u);
}
}
int lca(int a,int b){
if(a==b) return a;
if((a=Tdfn[a])>(b=Tdfn[b])) swap(a,b);
int k=loggg[b-a++];
return get(st[k][a],st[k][b-(1<<k)+1]);
}
void dfs2(int u,int p){
for(int i=Th[u];i;i=Tne[i]){
int v=Te[i];
if(v==p) continue;
dfs2(v,u);
ss[u]+=ss[v];
}
}
void modify(int x,int c){
for(;x<=n;x+=x&-x) tr[x]+=c;
}
int query(int x){
int res=0;
for(;x;x&=x-1) res+=tr[x];
return res;
}
void work()
{
for(int i=1;i<=n;i++) h[i]=dfn[i]=0; idx=2; timestamp=top=dcc_cnt=0;
for(int i=1;i<=m;i++) add(E[i].u,E[i].v);
Q=0; tarjan(1);
for(int i=1;i<=k;i++)
ka[i] = a[i],kb[i] = b[i];
for(int i=1;i<=dcc_cnt;i++) Th[i]=ss[i]=0; Tidx=2;
unordered_set<LL> S;
for(int i=1;i<=m;i++){
int a=e[i<<1],b=e[i<<1|1];
a=id[a],b=id[b];
if(a==b) continue;
if(S.count(get_hsh(a,b))) continue;
S.insert(get_hsh(a,b));
Tadd(a,b,i);
}
Ttimestamp=0; dfs1(1,0);
for(int i=2;i<=dcc_cnt;i++) loggg[i]=loggg[i>>1]+1;
for(int j=1;j<=17;j++) for(int i=1;i+(1<<j)-1<=dcc_cnt;i++) st[j][i]=get(st[j-1][i],st[j-1][i+(1<<j-1)]);
for(int i=1;i<=k;i++){
int a=id[ka[i]],b=id[kb[i]];
if(a==b) continue;
ss[a]++,ss[b]++,ss[lca(a,b)]-=2;
}
dfs2(1,0);
int MX1=0,MX2=0;
for(int i=2;i<=dcc_cnt;i++){
if(ss[i]>=ss[MX1]) MX2=MX1,MX1=i;
else if(ss[i]>ss[MX2]) MX2=i;
}
int RES=ss[MX1]+ss[MX2],E1=pe[MX1],E2=pe[MX2];
for(int I=1;I<=m;I++){
del[I<<1]=del[I<<1|1]=1;
for(int u=1;u<=n;u++) dfn[u]=0; timestamp=top=dcc_cnt=0;
Q=0; tarjan(1);
if(timestamp<n){del[I<<1]=del[I<<1|1]=0; continue;}
for(int i=1;i<=n;i++) vl[i].clear(),vr[i].clear(),opl[i].clear(),opr[i].clear(),cnt[i]=0;
for(int i=1;i<=Q;i++) vl[ls[i]].push_back(i),vr[rs[i]].push_back(i);
for(int i=1;i<=k;i++){
int a=dfn[ka[i]],b=dfn[kb[i]];
if(a>b) swap(a,b);
opl[a].push_back(b),opr[b].push_back(a);
}
for(int i=0;i<=n+1;i++) tr[i]=0;
for(int i=1;i<=n;i++){
for(int &u:vl[i]) cnt[u]+=query(rs[u])-query(ls[u]-1);
for(int &u:opl[i]) modify(u,1);
}
for(int i=0;i<=n+1;i++) tr[i]=0;
for(int i=n;i;i--){
for(int &u:vr[i]) cnt[u]+=query(rs[u])-query(ls[u]-1);
for(int &u:opr[i]) modify(u,1);
}
for(int i=1;i<=Q;i++) if(cnt[i]>RES) RES=cnt[i],E1=I,E2=ids[i];
del[I<<1]=del[I<<1|1]=0;
}
if(!E1&&!E2) E1=1,E2=2;
else if(!E1) E1=1+(E2==1);
else if(!E2) E2=1+(E1==1);
printf("%d\n%d %d\n%d %d\n",RES,e[E1<<1],e[E1<<1|1],e[E2<<1],e[E2<<1|1]);
}
}
namespace SOL2
{
int hs[N],siz[N],dep[N],fa[N],top[N],s[N],p[N];
void D1(int x)
{
siz[x] = 1,dep[x] = dep[fa[x]]+1;
for (int i = las[x],y;i;i = e[i].pre)
{
if ((y = e[i].to) != fa[x])
{
fa[y] = x,D1(y),siz[x] += siz[y];
if (siz[y] > siz[hs[x]]) hs[x] = y;
}
}
}
void D2(int x,int t)
{
top[x] = t;
if (hs[x]) D2(hs[x],t);
for (int i = las[x],y;i;i = e[i].pre)
if ((y = e[i].to) != fa[x] && y != hs[x]) D2(y,y);
}
inline int LCA(int x,int y)
{
while (top[x] != top[y]) dep[top[x]] < dep[top[y]] ? y = fa[top[y]] : x = fa[top[x]];
return dep[x] < dep[y] ? x : y;
}
void D(int x)
{
for (int i = las[x],y;i;i = e[i].pre)
if ((y = e[i].to) != fa[x]) D(y),s[x] += s[y];
}
void work()
{
for (int i = 1;i <= n;i++) s[i] = hs[i] = 0;
for (int i = 1;i <= m;i++) add(E[i].u,E[i].v),add(E[i].v,E[i].u);
D1(1),D2(1,1);
for (int i = 1;i <= k;i++) s[a[i]]++,s[b[i]]++,s[LCA(a[i],b[i])] -= 2;
D(1);
for (int i = 1;i <= n;i++) p[i] = i;
sort(p+1,p+n+1,[](int x,int y){return s[x] > s[y];});
printf("%d\n%d %d\n%d %d\n",s[p[1]]+s[p[2]],fa[p[1]],p[1],fa[p[2]],p[2]);
}
}
namespace SOL3
{
struct tree{int l,r,laz;pii mx;}tr[N<<2];
void build(int l,int r,int i)
{
tr[i].l = l,tr[i].r = r,tr[i].laz = 0;
if (l == r) return (void)(tr[i].mx = {0,l});
int mid = (l+r)>>1;
build(l,mid,i<<1),build(mid+1,r,i<<1|1);
tr[i].mx = max(tr[i<<1].mx,tr[i<<1|1].mx);
}
void down(int i)
{
tr[i<<1].mx.fr += tr[i].laz,tr[i<<1].laz += tr[i].laz;
tr[i<<1|1].mx.fr += tr[i].laz,tr[i<<1|1].laz += tr[i].laz;
tr[i].laz = 0;
}
void M(int l,int r,int v,int i)
{
if (l > r) return ;
if (tr[i].l >= l && tr[i].r <= r)
return (void)(tr[i].mx.fr += v,tr[i].laz += v);
if (tr[i].laz) down(i);
if (tr[i<<1].r >= l) M(l,r,v,i<<1);
if (tr[i<<1|1].l <= r) M(l,r,v,i<<1|1);
tr[i].mx = max(tr[i<<1].mx,tr[i<<1|1].mx);
}
struct node{int l,r,k;};
vector < node > ve[N];
int hs[N],siz[N],dep[N],fa[N],top[N],s[N],p[N],bl[N],pre[N],tot,r[N];
bool fl[N],vis[N],flg;
void D1(int x,int t)
{
siz[x] = 1,dep[x] = dep[fa[x]]+1,bl[x] = t;
for (int i = las[x],y;i;i = e[i].pre)
{
if ((y = e[i].to) != fa[x])
{
fa[y] = x,D1(y,t),siz[x] += siz[y];
if (siz[y] > siz[hs[x]]) hs[x] = y;
}
}
}
void D2(int x,int t)
{
top[x] = t;
if (hs[x]) D2(hs[x],t);
for (int i = las[x],y;i;i = e[i].pre)
if ((y = e[i].to) != fa[x] && y != hs[x]) D2(y,y);
}
inline int LCA(int x,int y)
{
while (top[x] != top[y]) dep[top[x]] < dep[top[y]] ? y = fa[top[y]] : x = fa[top[x]];
return dep[x] < dep[y] ? x : y;
}
void D(int x)
{
for (int i = las[x],y;i;i = e[i].pre)
if ((y = e[i].to) != fa[x]) D(y),s[x] += s[y];
}
void D3(int x)
{
if (flg) return ;
vis[x] = 1;
for (int i = las[x],y;i && !flg;i = e[i].pre)
{
if ((y = e[i].to) != pre[x])
{
if (vis[y])
{
flg = fl[x] = 1,r[++tot] = x;
while (x != y) x = pre[x],r[++tot] = x,fl[x] = 1;
break;
}
else pre[y] = x,D3(y);
}
}
}
void work()
{
tot = flg = 0;
for (int i = 1;i <= n;i++) s[i] = hs[i] = vis[i] = fl[i] = pre[i] = 0;
for (int i = 1;i <= m;i++) add(E[i].u,E[i].v),add(E[i].v,E[i].u);
D3(1),cnt = 0;
for (int i = 1;i <= n;i++) las[i] = 0;
for (int i = 1;i <= m;i++)
{
if (fl[E[i].u] && fl[E[i].v]) continue;
add(E[i].u,E[i].v),add(E[i].v,E[i].u);
}
for (int i = 1;i <= tot;i++) id[r[i]] = i,D1(r[i],r[i]),D2(r[i],r[i]);
for (int i = 1;i <= k;i++)
{
if (bl[a[i]] != bl[b[i]])
{
int x = bl[a[i]],y = bl[b[i]];
s[a[i]]++,s[x]--,s[b[i]]++,s[y]--;
x = id[x],y = id[y];
if (x > y) swap(x,y);
y--;
ve[1].pb({x,y,1}),ve[x].pb({x,y,-1}),ve[y+1].pb({x,y,1}),
ve[x].pb({1,x-1,1}),ve[x].pb({y+1,tot,1}),ve[y+1].pb({1,x-1,-1}),ve[y+1].pb({y+1,tot,-1});
}
else s[a[i]]++,s[b[i]]++,s[LCA(a[i],b[i])] -= 2;
}
int mx = 0,x,y;
build(1,tot,1);
for (int i = 1;i <= tot;i++)
{
for (auto [u,v,w] : ve[i]) M(u,v,w,1);
if (mx < tr[1].mx.fr) mx = tr[1].mx.fr,x = i,y = tr[1].mx.se;
ve[i].clear();
}
for (int i = 1;i <= tot;i++) D(r[i]);
for (int i = 1;i <= n;i++) p[i] = i;
sort(p+1,p+n+1,[](int x,int y){return s[x] > s[y];});
int v = s[p[1]]+s[p[2]];
if (v > mx) printf("%d\n%d %d\n%d %d\n",v,fa[p[1]],p[1],fa[p[2]],p[2]);
else printf("%d\n%d %d\n%d %d\n",mx,r[x],r[x%tot+1],r[y],r[y%tot+1]);
}
}
int main()
{
int Id = read(),T = read();
while (T--)
{
cnt = 0;
for (int i = 1;i <= n;i++) las[i] = 0;
n = read(),m = read();
for (int i = 1;i <= m;i++) E[i] = {read(),read()};
k = read();
for (int i = 1;i <= k;i++) a[i] = read(),b[i] = read();
if (Id <= 4) SOL1::work();
if (Id >= 5 && Id <= 8) SOL2::work();
if (Id >= 9 && Id <= 16) SOL3::work();
}
return 0;
}
C: 回文数
缺点:能不能准确的刻画问题?
注意:
一种最优放法是相同字符放一块是显然的,
2025.1.11
A: Tree
缺点:想到点分治之后大脑就宕机了?想到扫描右端点之后思绪就混乱了?
注意:
点分治不一定只能解决路径问题,例如本题即使是树上连通块但是可以转化为数点问题,很容易使用数据结构来维护。
另一种做法用线段树维护 \(V-E=1\) 也并不是没有想到,但是为什么一个比较简单的问题会想的那么复杂认为不可做呢?
B: Permutation
缺点:没思路,没考虑笛卡尔树
注意:
笛卡尔树上一个点会被消除当且仅当它的一个子树空了,这时下一轮他会被他的祖先消掉。
dp 设 \(f_{i,j,0/1}\) 代表大小为 \(i\) 的子树 \(j\) 轮被消完是否为左或右链上的点的方案数,转移时枚举左子树大小及消除轮数进行转移。
C: Bipartite
优点:二分答案,k 没用以及问题都转化完了。
缺点:没有想到 dp
注意:
正常做很容易想到 \(dp\) 的问题套上图论壳子之后就不会做了,即使把问题转化出来了思维还停留在图论,将可以直接暴力 dp 容易做到 \(O(n^2)\) 的问题写成了 \(O(n^2m)\) 的网络流问题。
而且 \(O(n^2)\) 的暴力 dp 用线段树优化成 \(O(n\log n)\) 的 dp 真的非常简单。
2025.1.17
A: Phigros
提醒:若开的并不多,那么 deque 时间和空间都碾爆 list
注意:
转移形如整体右移,删除最后一个数,在最开始添加一个数,使用 deque 维护,时间复杂度可将为 \(O(n^2)\)
另一种做法是枚举最大值进行 dp,时间复杂度 \(O(n^2)\)。
2025.1.23
A: 字符串出现统计题
缺点:认为跟字符串没关系,没想到用 SAM。
注意:
子串在原串中出现次数对原串建 SAM 后可轻松求得,扫描右端点,每次移动右端点在后缀链接树上暴力向上跳对每个等价类中的字符串用线段树进行修改,复杂度不对。
这种题考虑 DAG 链剖分,后面会学。
B:标准库资源管理器
注意:
根号分治时处理小块对大块的贡献只需加入小块时扫一遍会影响到的大块,这样复杂度就是正确的。
C: 但是题目有八个字
优点:场切了
注意:
从小到大合并很快就能看出来,空间限制 \(8MB\) 且输入升序比较容易想到是在输入的同时进行求解。
首先对于合并成的数 \(y\),若 \(x<y\) 那么一定不会造成贡献,那么造成贡献时一定是 \(x>y\),读入的同时贪心合并就能求得答案。
2025.1.25
A: 数环
优点:一眼就知道可以排序
缺点:dp 状态都没设计出来
注意:
二分图很多题都是 dp,通常的 dp 状态含有 \(i,j\) 代表处理到前 \(i\) 个左部点和前 \(j\) 个右部点。
本题考虑对于一个环来说,若保留前 \(i\) 个左部点和前 \(j\) 得右部点,这个环只会被断成若干条链,同时由于本题连边的特殊方式,我们可以把左右部点按一定顺序放到一个序列上来进行 dp。
具体地,设 \(f_{i,j}\) 为前 \(i\) 个点,选择了 \(j\) 条链的方案数,后面分当前点为左部点和右部点来进行 dp 。

浙公网安备 33010602011771号