好题整理
可能更好的观赏体验
前言:本篇文章整理的在洛谷上均为 \color{purple}{紫} - \color{black}{黑} 的难度的题目
UPDATE LOG
- 2023.1.4 开坑
- 2023.1.4 填坑 [POI2005]KOS-Dicing
- 2023.1.5 填坑 [SDOI2011]染色
- 2023.1.31 填坑 Unique Occurrences
1.[POI2005]KOS-Dicing(洛谷题面)
1.1 二分
做这道题之前,我们需要看出这道题是满足单调性的,换句话说,假设比赛最多的人比赛了
1.2 网络流
假设我们当前二分到了
1.3 方案输出
我们此时已经二分出来答案了,那么我们根据这个答案再建一次网络流,重新跑一遍,然后我们对于每场比赛,根据反悔操作,我们可以发现流向这里为
#include<bits/stdc++.h>
#define int long long
#define repe(i,l,r) for(int (i)=l;(i)<=r;(i)++)
#define rep(i,n) for(int (i)=1;(i)<=n;(i)++)
#define FOR(i,r,l) for(int (i)=r;(i)>=l;(i)--)
#define INF 0x3f3f3f
#define pii pair<int,int>
#define mpr make_pair
#define pb push_back
#define ALL(v) (v).begin(),(v).end()
#define rsort(v) sort(ALL(v),greater<int>())
#define lb(v,x) (int)(lower_bound(ALL(v),x)-v.begin())
#define ub(v,x) (int)(upper_bound(ALL(v),x)-v.begin())
#define uni(v) v.resize(unique(ALL(v))-v.begin())
using namespace std;
int read(){int sum=0,f=1;char c;c=getchar();while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}while(c>='0'&&c<='9'){sum=(sum<<1)+(sum<<3)+(c-'0');c=getchar();}return sum*f;}
void out(int x){if(x<0){x=-x;putchar('-');}if(x>=10)out(x/10);putchar(x%10+'0');}
template <typename T>void die(T s){cout<<s<<endl;exit(0);}
int fast(int a,int b,int P){int res=1;if(P<=0){while(b){if(b&1)res=res*a;a=a*a;b>>=1;}}else{while(b){if(b&1)res=res*a%P;a=a*a%P;b>>=1;}}return res;}
template <typename T>void chkmax(T& a,T b){if(a<b)a=b;return;}
template <typename T>void chkmin(T& a,T b){if(a>b)a=b;return;}
const int N=2e4+5,M=1e5+5;
struct Graph{
int head[N],nxt[M],to[M],limit[M],edge=1;
int cur[N],dis[N];
void init(){
edge=1;
memset(head,0,sizeof(head));
return;
}
void add(int u,int v,int w){
nxt[++edge]=head[u];
head[u]=edge;
to[edge]=v,limit[edge]=w;
}
int dfs(int u,int t,int res){
if(u==t)return res;
int flow=0;
for(int i=cur[u];i;i=nxt[i]){
cur[u]=i;
int c=min(res,limit[i]);
int v=to[i];
if(dis[v]==dis[u]+1&&c){
int k=dfs(v,t,c);
flow+=k,res-=k,limit[i]-=k,limit[i^1]+=k;
}
}
if(!flow)dis[u]=-1;
return flow;
}
int MaxFlow(int s,int t){
int ans=0;
while(1){
memcpy(cur,head,sizeof(head));
memset(dis,-1,sizeof(dis));
dis[s]=0;
queue<int> q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(dis[v]==-1&&limit[i]){
dis[v]=dis[u]+1;
q.push(v);
}
}
}
if(dis[t]==-1)return ans;
ans+=dfs(s,t,1e18);
}
}
}F;
int n,m;
vector<int> G[N];
pii ed[N];
bool check(int x){
int s=0,t=n+m+1;
F.init();
rep(i,n){
F.add(s,i,x);
F.add(i,s,0);
}
rep(i,m){
int u=ed[i].first,v=ed[i].second;
F.add(u,n+i,1);
F.add(n+i,u,0);
F.add(v,n+i,1);
F.add(n+i,v,0);
F.add(n+i,t,1);
F.add(t,n+1,0);
}
int k=F.MaxFlow(s,t);
return (k==m? 1:0);
}
signed main(){
n=read(),m=read();
rep(i,m){
int u=read(),v=read();
ed[i]=mpr(u,v);
}
int l=1,r=m,ans=m;
while(l<=r){
int mid=l+r>>1;
if(check(mid)){
ans=mid,r=mid-1;
}
else{
l=mid+1;
}
}
out(ans);
puts("");
check(ans);
for(int u=n+1;u<=n+m;u++){
for(int i=F.head[u];i;i=F.nxt[i]){
if(F.limit[i]){
int v=F.to[i];
if(v==ed[u-n].first)puts("1");
else puts("0");
}
}
}
return 0;
}
2.[SDOI2011]染色(洛谷题面)
2.1 树链剖分
观察下这句话:
- 将节点
a 到节点b 的路径上的所有点(包括a 和b )都染成颜色c 。
我们不难可以想出可以用树链剖分来维护树上线段树,然后进行区间修改,查询等操作。而区间修改比较简单,下面我们来将一下如何统计答案。
2.2 区间查询
首先是对于在线段树上的上传操作我们如何计算答案。假设我们现在要合并区间
- 每段区间的第一个和最后一个颜色,单独看这段区间的答案,分别记为
st,en,ans
那么我们在合并
然后对于树上链的答案合并也是同理。
#include<bits/stdc++.h>
#define int long long
#define repe(i,l,r) for(int (i)=l;(i)<=r;(i)++)
#define rep(i,n) for(int (i)=1;(i)<=n;(i)++)
#define FOR(i,r,l) for(int (i)=r;(i)>=l;(i)--)
#define INF 0x3f3f3f
#define pii pair<int,int>
#define mpr make_pair
#define pb push_back
#define ALL(v) (v).begin(),(v).end()
#define rsort(v) sort(ALL(v),greater<int>())
#define lb(v,x) (int)(lower_bound(ALL(v),x)-v.begin())
#define ub(v,x) (int)(upper_bound(ALL(v),x)-v.begin())
#define uni(v) v.resize(unique(ALL(v))-v.begin())
#define ls p<<1
#define rs p<<1|1
using namespace std;
int read(){int sum=0,f=1;char c;c=getchar();while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}while(c>='0'&&c<='9'){sum=(sum<<1)+(sum<<3)+(c-'0');c=getchar();}return sum*f;}
void out(int x){if(x<0){x=-x;putchar('-');}if(x>=10)out(x/10);putchar(x%10+'0');}
template <typename T>void die(T s){cout<<s<<endl;exit(0);}
int fast(int a,int b,int P){int res=1;if(P<=0){while(b){if(b&1)res=res*a;a=a*a;b>>=1;}}else{while(b){if(b&1)res=res*a%P;a=a*a%P;b>>=1;}}return res;}
template <typename T>void chkmax(T& a,T b){if(a<b)a=b;return;}
template <typename T>void chkmin(T& a,T b){if(a>b)a=b;return;}
const int N=100005;
int n,m;
vector<int> G[N];
int a[N],dfn[N],rnk[N],top[N],fa[N],son[N],sz[N];
int dep[N];
struct SegTree{
int st=0,sum=0,en=0,l=0,r=0,tag;
}tree[N<<2];
void up(int p){
tree[p].sum=tree[ls].sum+tree[rs].sum;
if(tree[rs].st==tree[ls].en)tree[p].sum--;
tree[p].st=tree[ls].st;
tree[p].en=tree[rs].en;
return;
}
void down(int p){
if(!tree[p].tag)return;
tree[ls].tag=tree[p].tag;
tree[ls].st=tree[p].tag;
tree[ls].en=tree[p].tag;
tree[ls].sum=1;
tree[rs].tag=tree[p].tag;
tree[rs].st=tree[p].tag;
tree[rs].en=tree[p].tag;
tree[rs].sum=1;
tree[p].tag=0;
return;
}
void build(int l,int r,int p){
tree[p].l=l,tree[p].r=r;
if(l==r){
tree[p].st=tree[p].en=a[rnk[l]];
tree[p].sum=1;
return;
}
int mid=l+r>>1;
build(l,mid,p<<1);
build(mid+1,r,p<<1|1);
up(p);
return;
}
int cnt;
void dfs1(int u,int f){
sz[u]=1;
fa[u]=f;
dep[u]=dep[f]+1;
for(auto v:G[u]){
if(v==f)continue;
dfs1(v,u);
sz[u]+=sz[v];
if(sz[son[u]]<sz[v])son[u]=v;
}
}
void dfs2(int u,int tp){
top[u]=tp;
dfn[u]=++cnt;
rnk[cnt]=u;
if(son[u])dfs2(son[u],tp);
for(auto v:G[u]){
if(v==fa[u]||v==son[u])continue;
dfs2(v,v);
}
}
void update(int l,int r,int x,int p){
if(l<=tree[p].l&&r>=tree[p].r){
tree[p].st=x;tree[p].en=x;
tree[p].sum=1;
tree[p].tag=x;
return;
}
down(p);
int mid=tree[p].l+tree[p].r>>1;
if(r<=mid)update(l,r,x,p<<1);
else if(l>mid)update(l,r,x,p<<1|1);
else{
update(l,mid,x,p<<1);
update(mid+1,r,x,p<<1|1);
}
up(p);
return;
}
SegTree query(int l,int r,int p){
if(l<=tree[p].l&&r>=tree[p].r){
return tree[p];
}
down(p);
int mid=tree[p].l+tree[p].r>>1;
if(r<=mid)return query(l,r,p<<1);
else if(l>mid)return query(l,r,p<<1|1);
else{
SegTree x=query(l,mid,p<<1);
SegTree y=query(mid+1,r,p<<1|1);
SegTree res;
res.sum=x.sum+y.sum;
res.st=x.st;
res.en=y.en;
if(x.en==y.st)res.sum--;
return res;
}
}
void calc(int x,int y,int c){
int fx=top[x],fy=top[y];
while(fx!=fy){
if(dep[top[x]]>=dep[top[y]]){
update(dfn[fx],dfn[x],c,1);
x=fa[fx];
}
else{
update(dfn[fy],dfn[y],c,1);
y=fa[fy];
}
fx=top[x],fy=top[y];
}
if(dfn[x]<dfn[y]){
update(dfn[x],dfn[y],c,1);
}
else{
update(dfn[y],dfn[x],c,1);
}
return;
}
int querysum(int x,int y){
int ans=0,fx=top[x],fy=top[y];
SegTree prex,prey;
while(fx!=fy){
if(dep[top[x]]>=dep[top[y]]){
SegTree nowx=query(dfn[fx],dfn[x],1);
ans+=nowx.sum;
if(prex.st==nowx.en)ans--;
x=fa[fx];
prex=nowx;
}
else{
SegTree nowy=query(dfn[fy],dfn[y],1);
ans+=nowy.sum;
if(prey.st==nowy.en)ans--;
y=fa[fy];
prey=nowy;
}
fx=top[x];
fy=top[y];
}
if(dfn[x]<dfn[y]){
SegTree a=query(dfn[x],dfn[y],1);
ans+=a.sum;
if(a.st==prex.st)ans--;
if(a.en==prey.st)ans--;
}
else{
SegTree a=query(dfn[y],dfn[x],1);
ans+=a.sum;
if(a.en==prex.st)ans--;
if(a.st==prey.st)ans--;
}
return ans;
}
signed main(){
scanf("%lld %lld",&n,&m);
rep(i,n)scanf("%lld",a+i);
rep(i,n-1){
int u,v;
scanf("%lld %lld",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs1(1,0);
dfs2(1,0);
build(1,n,1);
while(m--){
char op;
cin>>op;
if(op=='C'){
int a,b,c;
scanf("%lld %lld %lld",&a,&b,&c);
calc(a,b,c);
}
else{
int a,b;
scanf("%lld %lld",&a,&b);
printf("%lld\n",querysum(a,b));
}
}
return 0;
}
3.Unique Occurrences(洛谷题面)
3.1 计算
发现直接计算不容易做,可以考虑对于每个边权求贡献。对于一个条边,其贡献为
3.2 并查集
考虑用可撤销并查集进行分治,对于一种颜色,我们可以用并查集将不等于这个边权的边用并查集连接起来。用递归分治进行维护,最后计算答案。
#include<bits/stdc++.h>
#define int long long
#define repe(i,l,r) for(int (i)=l;(i)<=r;(i)++)
#define rep(i,n) for(int (i)=1;(i)<=n;(i)++)
#define FOR(i,r,l) for(int (i)=r;(i)>=l;(i)--)
#define INF 0x3f3f3f
#define pii pair<int,int>
#define mpr make_pair
#define pb push_back
#define ALL(v) (v).begin(),(v).end()
#define rsort(v) sort(ALL(v),greater<int>())
#define lb(v,x) (int)(lower_bound(ALL(v),x)-v.begin())
#define ub(v,x) (int)(upper_bound(ALL(v),x)-v.begin())
#define uni(v) v.resize(unique(ALL(v))-v.begin())
using namespace std;
int read(){int sum=0,f=1;char c;c=getchar();while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}while(c>='0'&&c<='9'){sum=(sum<<1)+(sum<<3)+(c-'0');c=getchar();}return sum*f;}
void out(int x){if(x<0){x=-x;putchar('-');}if(x>=10)out(x/10);putchar(x%10+'0');}
template <typename T>void die(T s){cout<<s<<endl;exit(0);}
int fast(int a,int b,int P){int res=1;if(P<=0){while(b){if(b&1)res=res*a;a=a*a;b>>=1;}}else{while(b){if(b&1)res=res*a%P;a=a*a%P;b>>=1;}}return res;}
template <typename T>void chkmax(T& a,T b){if(a<b)a=b;return;}
template <typename T>void chkmin(T& a,T b){if(a>b)a=b;return;}
int n,fa[500005],sz[500005];
vector<pii> G[500005];
int getfa(int x){
if(x==fa[x])return x;
return getfa(fa[x]);
}
int rk[100005];
void Merge(int x,int y,vector<pii>& v){
x=getfa(x),y=getfa(y);
if(x==y)return;
if(sz[x]>sz[y])swap(x,y);
fa[x]=y;
sz[y]+=sz[x];
v.push_back(mpr(x,y));
}
void del(vector<pii>& v){
while(v.size()){
int top=v.size();
int x=v[top-1].first,y=v[top-1].second;
fa[x]=x,sz[y]-=sz[x];
v.pop_back();
}
return;
}
int ans;
void solve(int l,int r){
if(l==r){
for(auto ed:G[l]){
int x=getfa(ed.first),y=getfa(ed.second);
ans=(ans+sz[x]*sz[y]);
}
return;
}
int mid=l+r>>1;
vector<pii> v;
repe(i,mid+1,r){
for(auto ed:G[i]){
Merge(ed.first,ed.second,v);
}
}
solve(l,mid);
del(v);
repe(i,l,mid){
for(auto ed:G[i]){
Merge(ed.first,ed.second,v);
}
}
solve(mid+1,r);
del(v);
return;
}
signed main(){
n=read();
rep(i,n-1){
int u=read(),v=read(),w=read();
G[w].pb(mpr(u,v));
}
rep(i,n)sz[i]=1,fa[i]=i;
solve(1,n);
out(ans);
return 0;
}
4.[AGC003F] Fraction of Fractal(洛谷题面)
下文定义
4.1 分类讨论
对于这道题,直觉告诉我们连通块的个数取决于边界上的点对。那么我们可以分类讨论。
4.1.1 情况一
对于上下左右都没有对应的黑块对,很显然的,最后连通块的个数取决于
4.1.2 情况二
和情况一完全相反,上下左右都有对应的点对,显然怎么复制答案都是
4.1.3 情况三
如果只有上下或左右有对应的话,这时候就有些复杂了。显然的,当我们每次复制的时候,答案将会乘上
最后用矩阵快速幂解决即可。
#include<bits/stdc++.h>
#define int long long
#define repe(i,l,r) for(int (i)=l;(i)<=r;(i)++)
#define rep(i,n) for(int (i)=1;(i)<=n;(i)++)
#define FOR(i,r,l) for(int (i)=r;(i)>=l;(i)--)
#define INF 0x3f3f3f
#define pii pair<int,int>
#define mpr make_pair
#define pb push_back
#define ALL(v) (v).begin(),(v).end()
#define rsort(v) sort(ALL(v),greater<int>())
#define lb(v,x) (int)(lower_bound(ALL(v),x)-v.begin())
#define ub(v,x) (int)(upper_bound(ALL(v),x)-v.begin())
#define uni(v) v.resize(unique(ALL(v))-v.begin())
#define debug(x) cerr<<#x<<':'<<x<<endl;
using namespace std;
int read(){int sum=0,f=1;char c;c=getchar();while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}while(c>='0'&&c<='9'){sum=(sum<<1)+(sum<<3)+(c-'0');c=getchar();}return sum*f;}
void out(int x){if(x<0){x=-x;putchar('-');}if(x>=10)out(x/10);putchar(x%10+'0');}
template <typename T>void die(T s){cout<<s<<endl;exit(0);}
int fast(int a,int b,int P){int res=1;if(P<=0){while(b){if(b&1)res=res*a;a=a*a;b>>=1;}}else{while(b){if(b&1)res=res*a%P;a=a*a%P;b>>=1;}}return res;}
template <typename T>void chkmax(T& a,T b){if(a<b)a=b;return;}
template <typename T>void chkmin(T& a,T b){if(a>b)a=b;return;}
int h,w,k;
char s[1005][1005];
int cnt1,cnt2,block,num;
const int P=1e9+7;
struct Matrix{
int a[4][4];
Matrix(){memset(a,0,sizeof(a));}
void init(){a[1][1]=a[2][2]=1;}
}st1,st2,ans;
Matrix operator*(Matrix a,Matrix b){
Matrix c;
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++)
for(int k=1;k<=2;k++)
c.a[i][j]=((c.a[i][j]+a.a[i][k]*b.a[k][j])%P+P)%P;
return c;
}
Matrix ksm(Matrix A,int b){
Matrix res;
res.init();
while(b){
if(b&1)res=res*A;
A=A*A;
b>>=1;
}
return res;
}
signed main(){
h=read(),w=read(),k=read();
for(int i=1;i<=h;i++)scanf("%s",s[i]+1);
rep(i,h)rep(j,w)block+=s[i][j]=='#';
rep(i,h){
if(s[i][1]==s[i][w]&&s[i][w]=='#')cnt1++;
}
rep(i,w){
cnt2+=(s[1][i]==s[h][i]&&s[1][i]=='#');
}
if(cnt1&&cnt2)die("1");
else if(!cnt1&&!cnt2)die(fast(block,k-1,P));
else{
if(cnt1){
for(int i=1;i<=h;i++){
for(int j=1;j<w;j++){
num+=(s[i][j]==s[i][j+1]&&s[i][j]=='#');
}
}
}
else{
for(int j=1;j<=w;j++){
for(int i=1;i<h;i++){
num+=(s[i][j]==s[i+1][j]&&s[i][j]=='#');
}
}
}
// cout<<block<<' '<<num<<' '<<cnt1+cnt2<<endl;
st2.a[1][1]=st2.a[1][2]=1;
st1.a[1][1]=block;
st1.a[2][1]=-num;
st1.a[2][2]=cnt1+cnt2;
ans=st2*ksm(st1,k-1);
out(ans.a[1][1]);
}
return 0;
}
5.Non-equal Neighbours
前言:本题为 CF1591F,一模一样题目还有 CF1585F 和 ARC115E(数据范围有改动)。其中两题洛谷评蓝,一题评紫,笔者认为难度更加偏向紫一些。
5.1 容斥
这道题如果直接计算答案,显然是不容易计算的。正难则反反更难,我们可以令函数
5.2 思路转化
但是考虑完容斥之后,我们会发现这道题仍然
读者可以思考一下,为什么这个动态规划方程表示的是
5.3 空间时间优化
先咕着,有空时再写