2022.11.10题解
一测:\(86pts\),差点搞出 T2。
T1:P5038 [SCOI2012]奇怪的游戏(紫)
T2:P5842 [SCOI2012]Blinker 的仰慕者(黑)
T3:P2573 [SCOI2012]滑雪(蓝)
T4:P4416 [COCI2017-2018#1] Plahte(紫)
T1:
题意简述:
给定 \(n\times m\) 的矩阵,每次可以给相邻的两个数 \(+1\),求最少操作次数使矩阵中所有数相同,无解输出 \(-1\)。
解:
很明显的相邻黑白染色网络流。
对于 \(n\times m\) 为偶数,那么就很好做,因为如果所有数相同,我们可以使其中的所有数都正好 \(+1\),那么答案就有单调性,直接二分答案跑网络流即可。
网络流即相邻的点建无限流边,源点对黑节点建黑节点值为流量的边,而白节点对汇点建白节点到目标值的差值为流量的边,然后跑最小割,看所有有限流量边是否全被割完即可。
难点在于若 \(n\times m\) 为奇数,那么我们就无法对所有节点正好 \(+1\)。
这个时候我们就设最终答案为 \(x\),那么 \(x\times num_{black}-sum_{black}=x\times num_{white}-sum_{white}\),\(num\) 代表节点个数,\(sum\) 代表节点和。
那么这个方程是可以解出来的,就去验证 \(x\) 是否合法即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define int long long
using namespace std;
const int N=1e6+5;
const int M=105;
int n,m,c[M][M],h[M*M],cnt,s,t,vis[M*M],dis[M*M],ht[M*M];
queue<int>q;
struct node
{
int to,data,next;
}a[N];
void addedge(int u,int v,int w)
{
//cout<<u<<" "<<v<<" "<<w<<endl;
a[cnt]={v,w,h[u]};
h[u]=cnt++;
a[cnt]={u,0,h[v]};
h[v]=cnt++;
}
inline int calc(int x,int y)
{
return (x-1)*m+y;
}
void build(int x)
{
for(int i=1;i<=n;i++)
{
bool p=i%2;
for(int j=1;j<=m;j++)
{
if(p%2==1)addedge(s,calc(i,j),x-c[i][j]);
else addedge(calc(i,j),t,x-c[i][j]);
p^=1;
}
}
}
int getnum()
{
int num1=0,num2=0,sum1=0,sum2=0;
for(int i=1;i<=n;i++)
{
int p=i%2;
for(int j=1;j<=m;j++)
{
if(p==1)num1++,sum1+=c[i][j];
else num2++,sum2+=c[i][j];
p^=1;
}
}
if(num1!=num2)return (sum1-sum2)/(num1-num2);
return sum1-sum2;
}
bool bfs()
{
for(int i=1;i<=t;i++)dis[i]=0;
q.push(s);
dis[s]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=h[x];i!=-1;i=a[i].next)
{
if(a[i].data==0||dis[a[i].to])continue;
dis[a[i].to]=dis[x]+1;
q.push(a[i].to);
}
}
return dis[t];
}
int dfs(int x,int num)
{
if(x==t)return num;
if(vis[x])return 0;
vis[x]=1;
for(int i=h[x];i!=-1;i=a[i].next)
{
if(!a[i].data)continue;
if(dis[a[i].to]!=dis[x]+1)continue;
int res=dfs(a[i].to,min(a[i].data,num));
if(res)
{
a[i].data-=res;
a[i^1].data+=res;
return res;
}
}
return 0;
}
bool dinic()
{
int ans=0;
while(bfs())
{
for(int i=1;i<=t;i++)vis[i]=0;
int num=dfs(s,1e18);
while(num)
{
ans+=num;
for(int i=1;i<=t;i++)vis[i]=0;
num=dfs(s,1e18);
}
}
bfs();
for(int i=h[s];i!=-1;i=a[i].next)if(a[i].data)return false;
for(int i=h[t];i!=-1;i=a[i].next)if(a[i^1].data)return false;
return true;
}
int getans(int x)
{
int ans=0;
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)ans+=x-c[i][j];
if(ans%2==1)return -1;
return ans/2;
}
signed main()
{
//freopen("game.in","r",stdin);
//freopen("game.out","w",stdout);
int T;
scanf("%lld",&T);
while(T--)
{
scanf("%lld%lld",&n,&m);
s=n*m+1,t=n*m+2;
memset(h,-1,sizeof(h));
int maxn=0,ans=0;
cnt=0;
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%lld",&c[i][j]),maxn=max(maxn,c[i][j]);
for(int i=1;i<=n;i++)
{
bool p=i%2;
for(int j=1;j<=m;j++)
{
if(p==1)
{
if(i>1)addedge(calc(i,j),calc(i-1,j),1e18);
if(j>1)addedge(calc(i,j),calc(i,j-1),1e18);
if(i<n)addedge(calc(i,j),calc(i+1,j),1e18);
if(j<m)addedge(calc(i,j),calc(i,j+1),1e18);
}
p^=1;
}
}
if(n*m%2)
{
int x=getnum();
if(x<maxn)
{
printf("-1\n");
continue;
}
build(x);
int ans=-1;
if(dinic())ans=getans(x);
printf("%lld\n",ans);
continue;
}
if(getnum())
{
printf("-1\n");
continue;
}
int l=maxn,r=1e10+5;
while(l<r)
{
int mid=(l+r)>>1;
int num=cnt;
for(int i=1;i<=t;i++)ht[i]=h[i];
build(mid);
if(dinic())r=mid;
else l=mid+1;
cnt=num;
for(int i=1;i<=t;i++)h[i]=ht[i];
for(int i=0;i<=cnt;i+=2)a[i].data=1e18,a[i^1].data=0;
}
ans=-1;
build(l);
if(dinic())ans=getans(l);
printf("%lld\n",ans);
}
return 0;
}
/*
1
4 4
1 0 0 1
0 1 1 0
1 0 0 1
0 1 1 0
*/
T2:
题意简述:
求 \([l,r]\) 内各个数位积为 \(k\) 的数的和。
解:
在看题解前,请先确认自己是否精通了数位 dp 模板题,否则会很难理解。
对于朴素的数位 dp,我们可以记录 \(f_{i,j}\) 代表枚举到第 \(x\) 位,还需要的乘积为 \(j\) 的数之和。
只有一个状态处理数的个数,但本题要求数的和,这样我们比较难用一位去处理。
所以再定义 \(g_{i,j}\) 为枚举到第 \(x\) 位,还需要的乘积为 \(j\) 的数个数。
那么合并上来即是:
\(g_{i,j}=\sum g_{i-1,j/k}\)
\(f_{i,j}=\sum f_{i-1,j/k}+k\times g_{i-1,j/k}\times 10^{i-1}\)
那么直接枚举向下搜索即可,如果搜到底部 \(j=1\),那么所有应该除的数已除完,直接返回和为 \(0\),个数为 \(1\),否则返回个数为 \(0\)。
特殊处理一下 \(K=0\)。
当 \(K=0\) 时就记录状态为当前数是否有 \(0\) 这一位,然后同样记录 \(f,g\) 两个数组的值,往下搜即可。
用 map 存储记忆化来解决空间问题,期望得分 \(20pts\)。(不愧是黑,部分分都那么难拿)。
考虑朴素 dp 的缺陷。
1、调用 map 开销大量时间。
2、有许多冗余状态。
说白了就是数存多了,所以我们对此进行优化。
由于数位只有 \(0\) 到 \(9\),那么质数只有 \(2,3,5,7\),所以对我们来说有用的数仅仅只有 \(2^a\times3^b\times5^c\times7^d\)。
那么我们可以把状态压成 \(f_{i,a,b,c,d}\) 代表枚举到第 \(i\) 位我们还剩下的因子为 \(2^a\times3^b\times5^c\times7^d\) 的和,\(g_{i,a,b,c,d}\) 同理。
转移比较容易,直接预处理出来每个数包含哪些质因子,在对应的质因子上减去即可。
期望得分:\(50pts\),实际得分:\(20pts\),被卡空间了。
#include<iostream>
#include<cstdio>
#include<cstring>
#define int long long
using namespace std;
const int mod=20120427;
int n,cnt,a[20],f[20][55][37][20][20],g[20][55][37][20][20],t2,t3,t5,t7,power[20],k,f2[20][2][2][2],g2[20][2][2][2];
int s[10][4]={
{0,0,0,0},
{0,0,0,0},
{1,0,0,0},
{0,1,0,0},
{2,0,0,0},
{0,0,1,0},
{1,1,0,0},
{0,0,0,1},
{3,0,0,0},
{0,2,0,0}
};
pair<int,int>dfs(int x,int k2,int k3,int k5,int k7,bool flag,bool zero,bool zero2)
{
//cout<<x<<" "<<k2<<" "<<k3<<" "<<k5<<" "<<k7<<" "<<flag<<" "<<zero<<" "<<f[x][k2][k3][k5][k7]<<endl;
if(x==0&&zero2)return {0,1};
if(x==0&&!k2&&!k3&&!k5&&!k7)return {0,1};
if(x==0)return {0,0};
if(!flag&&!zero&&f[x][k2][k3][k5][k7]!=-1)return {f[x][k2][k3][k5][k7],g[x][k2][k3][k5][k7]};
int ed=9;
if(flag)ed=a[x];
int sum=0,tot=0;
for(int i=0;i<=ed;i++)
{
if(!zero&&i==0&&k!=0)continue;
int p2=k2-s[i][0],p3=k3-s[i][1],p5=k5-s[i][2],p7=k7-s[i][3];
if((p2<0||p3<0||p5<0||p7<0))continue;
pair<int,int>res=dfs(x-1,p2,p3,p5,p7,flag&(i==a[x]),zero&(i==0),zero2|((!zero)&(!i)));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
if(!flag&&!zero)f[x][k2][k3][k5][k7]=sum,g[x][k2][k3][k5][k7]=tot;
return {sum,tot};
}
pair<int,int>dfs2(int x,bool flag,bool zero,bool zero2)
{
if(x==0)return {0,zero2};
if(f2[x][flag][zero][zero2]!=-1)return {f2[x][flag][zero][zero2],g2[x][flag][zero][zero2]};
int ed=9;
if(flag)ed=a[x];
int tot=0,sum=0;
for(int i=0;i<=ed;i++)
{
pair<int,int>res=dfs2(x-1,flag&(i==a[x]),zero&(i==0),zero2|((!zero)&(!i)));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
f2[x][flag][zero][zero2]=sum,g2[x][flag][zero][zero2]=tot;
return {sum,tot};
}
int solve(int x)
{
cnt=0;
while(x)
{
a[++cnt]=x%10;
x/=10;
}
if(k==0)
{
memset(f2,-1,sizeof(f2));
memset(g2,0,sizeof(g2));
int res=dfs2(cnt,1,1,0).first;
return res;
}
int t=k;
t2=t3=t5=t7=0;
while(t%2==0)t/=2,t2++;
while(t%3==0)t/=3,t3++;
while(t%5==0)t/=5,t5++;
while(t%7==0)t/=7,t7++;
//if(t2>3*cnt||t3>2*cnt||t5>cnt||t7>cnt)return 0;
if(k!=0&&t!=1)return 0;
for(int i=0;i<=cnt;i++)
for(int j=0;j<=t2;j++)
for(int sp=0;sp<=t3;sp++)
for(int q=0;q<=t5;q++)
for(int l=0;l<=t7;l++)f[i][j][sp][q][l]=-1,g[i][j][sp][q][l]=0;
int res=dfs(cnt,t2,t3,t5,t7,1,1,0).first;
return res;
}
signed main()
{
// freopen("fans.in","r",stdin);
// freopen("fans.out","w",stdout);
scanf("%lld",&n);
power[0]=1;
for(int i=1;i<=18;i++)power[i]=power[i-1]*10,power[i]%=mod;
for(int i=1;i<=n;i++)
{
int l,r;
scanf("%lld%lld%lld",&l,&r,&k);
int ans=solve(r)-solve(l-1);
ans=(ans%mod+mod)%mod;
printf("%lld\n",ans);
}
return 0;
}
考虑上述方法缺陷:有特别多状态是用不到的,如 \(2^{54}\times 3^{36}\),而我们把它们都压入了数组。
解决上述问题:直接预处理出来 \(10^{18}\) 以内满足 \(2^a\times3^b\times5^c\times7^d\) 的数,直接循环枚举就好。
那么现在我们转移状态时要知道下一个状态的编号为多少,这个提前用二分预处理出来即可,注意清空数组时得用栈先存储哪些状态发生了变化,再清空。
期望得分:\(100pts\),实际得分:\(50pts\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define int long long
using namespace std;
const int mod=20120427;
const int N=7e4+5;
const int M=1e18+5;
int n,cnt,a[20],f[20][N],g[20][N],t2,t3,t5,t7,power[20],f2[20][2][2][2],g2[20][2][2][2],num[N],lent,to[N][10],cnp;
int s[10][4]={
{0,0,0,0},
{0,0,0,0},
{1,0,0,0},
{0,1,0,0},
{2,0,0,0},
{0,0,1,0},
{1,1,0,0},
{0,0,0,1},
{3,0,0,0},
{0,2,0,0}
};
pair<int,int>q[N];
pair<int,int>dfs(int x,int k,bool flag,bool zero)
{
if(k==0)return {0,0};
//cout<<x<<" "<<k<<" "<<flag<<" "<<zero<<endl;
if(x==0&&k==1)return {0,1};
if(x==0)return {0,0};
if(!flag&&!zero&&f[x][k]!=-1)return {f[x][k],g[x][k]};
int ed=9;
if(flag)ed=a[x];
int sum=0,tot=0;
for(int i=0;i<=ed;i++)
{
if(!zero&&i==0)continue;
if(!to[k][i])continue;
pair<int,int>res=dfs(x-1,to[k][i],flag&(i==a[x]),zero&(i==0));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
if(!flag&&!zero)f[x][k]=sum,g[x][k]=tot,q[++cnp]={x,k};
return {sum,tot};
}
pair<int,int>dfs2(int x,bool flag,bool zero,bool zero2)
{
if(x==0)return {0,zero2};
if(f2[x][flag][zero][zero2]!=-1)return {f2[x][flag][zero][zero2],g2[x][flag][zero][zero2]};
int ed=9;
if(flag)ed=a[x];
int tot=0,sum=0;
for(int i=0;i<=ed;i++)
{
pair<int,int>res=dfs2(x-1,flag&(i==a[x]),zero&(i==0),zero2|((!zero)&(!i)));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
f2[x][flag][zero][zero2]=sum,g2[x][flag][zero][zero2]=tot;
return {sum,tot};
}
int numfind(int x)
{
int l=1,r=lent;
while(l<r)
{
int mid=(l+r)>>1;
if(num[mid]<x)l=mid+1;
else r=mid;
}
return l;
}
int solve(int x,int k)
{
cnt=0;
while(x)
{
a[++cnt]=x%10;
x/=10;
}
if(k==0)
{
memset(f2,-1,sizeof(f2));
memset(g2,0,sizeof(g2));
int res=dfs2(cnt,1,1,0).first;
return res;
}
int t=k;
t2=t3=t5=t7=0;
while(t%2==0)t/=2,t2++;
while(t%3==0)t/=3,t3++;
while(t%5==0)t/=5,t5++;
while(t%7==0)t/=7,t7++;
if(t!=1)return 0;
cnp=0;
int res=dfs(cnt,numfind(k),1,1).first;
for(int i=cnp;i>0;i--)f[q[i].first][q[i].second]=-1,g[q[i].first][q[i].second]=0;
return res;
}
void prepare()
{
int num2=1;
for(int i=0;i<=54;i++)
{
if(num2>M)break;
int num3=1;
for(int j=0;j<=36;j++)
{
if(num2*num3>M)break;
int num5=1;
for(int q=0;q<=18;q++)
{
if(num2*num3*num5>M)break;
int num7=1;
for(int l=0;l<=18;l++)
{
if(num2*num3*num5*num7>M)break;
num[++lent]=num2*num3*num5*num7;
num7*=7;
}
num5*=5;
}
num3*=3;
}
num2*=2;
}
sort(num+1,num+1+lent);
for(int i=1;i<=lent;i++)
{
to[i][0]=to[i][1]=i;
for(int j=2;j<=9;j++)
{
if(num[i]%j)continue;
to[i][j]=numfind(num[i]/j);
}
}
}
int read()
{
char ch=getchar();
int sum=0;
while(ch>'9'||ch<'0')ch=getchar();
while(ch>='0'&&ch<='9')sum=(sum<<1)+(sum<<3)+(ch^48),ch=getchar();
return sum;
}
void write(int x)
{
if(x/10)write(x/10);
putchar((x%10)^48);
}
signed main()
{
//freopen("fans.in","r",stdin);
//freopen("fans.out","w",stdout);
prepare();
n=read();
power[0]=1;
memset(f,-1,sizeof(f));
for(int i=1;i<=18;i++)power[i]=power[i-1]*10,power[i]%=mod;
for(int i=1;i<=n;i++)
{
int l,r,k;
l=read(),r=read(),k=read();
int ans=solve(r,k)-solve(l-1,k);
ans=(ans%mod+mod)%mod;
write(ans);
putchar('\n');
}
return 0;
}
那么剩下的也不好弄了,直接上剪枝。
记录一下每个状态搜到 \(1\) 的最小步数,如果现在状态的步数已经大于了剩下的位数,说明无法达到,直接返回。
记录最小步数就从自己可能的下一个状态转移上来,取最小值。
期望得分:\(100pts\),实际得分:\(100pts\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#define int long long
using namespace std;
const int mod=20120427;
const int N=7e4+5;
const int M=1e18+5;
int n,cnt,a[20],f[20][N],g[20][N],t2,t3,t5,t7,power[20],f2[20][2][2][2],g2[20][2][2][2],num[N],lent,to[N][10],cnp,dep[N];
pair<int,int>q[N];
pair<int,int>dfs(int x,int k,bool flag,bool zero)
{
if(k==0)return {0,0};
if(dep[k]>x)return {0,0};
//cout<<x<<" "<<k<<" "<<flag<<" "<<zero<<endl;
if(x==0&&k==1)return {0,1};
if(x==0)return {0,0};
if(!flag&&!zero&&f[x][k]!=-1)return {f[x][k],g[x][k]};
int ed=9;
if(flag)ed=a[x];
int sum=0,tot=0;
for(int i=0;i<=ed;i++)
{
if(!zero&&i==0)continue;
if(!to[k][i])continue;
pair<int,int>res=dfs(x-1,to[k][i],flag&(i==a[x]),zero&(i==0));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
if(!flag&&!zero)f[x][k]=sum,g[x][k]=tot,q[++cnp]={x,k};
return {sum,tot};
}
pair<int,int>dfs2(int x,bool flag,bool zero,bool zero2)
{
if(x==0)return {0,zero2};
if(f2[x][flag][zero][zero2]!=-1)return {f2[x][flag][zero][zero2],g2[x][flag][zero][zero2]};
int ed=9;
if(flag)ed=a[x];
int tot=0,sum=0;
for(int i=0;i<=ed;i++)
{
pair<int,int>res=dfs2(x-1,flag&(i==a[x]),zero&(i==0),zero2|((!zero)&(!i)));
tot+=res.second;
tot%=mod;
sum+=res.first+i*power[x-1]%mod*res.second%mod;
sum%=mod;
}
f2[x][flag][zero][zero2]=sum,g2[x][flag][zero][zero2]=tot;
return {sum,tot};
}
int numfind(int x)
{
int l=1,r=lent;
while(l<r)
{
int mid=(l+r)>>1;
if(num[mid]<x)l=mid+1;
else r=mid;
}
return l;
}
int solve(int x,int k)
{
cnt=0;
while(x)
{
a[++cnt]=x%10;
x/=10;
}
if(k==0)
{
memset(f2,-1,sizeof(f2));
memset(g2,0,sizeof(g2));
int res=dfs2(cnt,1,1,0).first;
return res;
}
int t=k;
t2=t3=t5=t7=0;
while(t%2==0)t/=2,t2++;
while(t%3==0)t/=3,t3++;
while(t%5==0)t/=5,t5++;
while(t%7==0)t/=7,t7++;
if(t!=1)return 0;
cnp=0;
int res=dfs(cnt,numfind(k),1,1).first;
for(int i=cnp;i>0;i--)f[q[i].first][q[i].second]=-1,g[q[i].first][q[i].second]=0;
return res;
}
void prepare()
{
int num2=1;
for(int i=0;i<=54;i++)
{
if(num2>M)break;
int num3=1;
for(int j=0;j<=36;j++)
{
if(num2*num3>M)break;
int num5=1;
for(int q=0;q<=18;q++)
{
if(num2*num3*num5>M)break;
int num7=1;
for(int l=0;l<=18;l++)
{
if(num2*num3*num5*num7>M)break;
num[++lent]=num2*num3*num5*num7;
num7*=7;
}
num5*=5;
}
num3*=3;
}
num2*=2;
}
sort(num+1,num+1+lent);
for(int i=1;i<=lent;i++)
{
if(i!=1)dep[i]=1e18;
to[i][0]=to[i][1]=i;
for(int j=2;j<=9;j++)
{
if(num[i]%j)continue;
int lim=numfind(num[i]/j);
to[i][j]=lim;
dep[i]=min(dep[i],dep[lim]+1);
}
}
}
int read()
{
char ch=getchar();
int sum=0;
while(ch>'9'||ch<'0')ch=getchar();
while(ch>='0'&&ch<='9')sum=(sum<<1)+(sum<<3)+(ch^48),ch=getchar();
return sum;
}
void write(int x)
{
if(x/10)write(x/10);
putchar((x%10)^48);
}
signed main()
{
//freopen("fans.in","r",stdin);
//freopen("fans.out","w",stdout);
prepare();
n=read();
power[0]=1;
memset(f,-1,sizeof(f));
for(int i=1;i<=18;i++)power[i]=power[i-1]*10,power[i]%=mod;
for(int i=1;i<=n;i++)
{
int l,r,k;
l=read(),r=read(),k=read();
int ans=solve(r,k)-solve(l-1,k);
ans=(ans%mod+mod)%mod;
write(ans);
putchar('\n');
}
return 0;
}
T3:
题意简述:
给一个带权有向图,每个点有一个高度,有向边的方向为从高向低,如果高度一样就建双向边,求这个有向图的以 \(1\) 为根的最小生成树(即自己的前继不能为自己的子节点,且节点数量尽可能多,边之和尽可能少)。
解:
比较简单,先搜出自己能走到的点,然后由于要把所有能到达的点都遍历一遍,所以高度越高的优先,那么按照边的后继的高度以第一关键字排序,边长为第二关键字排序,跑最小生成树即可。
考场上去干 T2 了。
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<stack>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N=1e5+5;
int n,m,h[N],f[N],cnt,dfn[N];
vector<pii>a[N];
struct node
{
int from,to,data;
}d[N*10];
int cmp(node fi,node se)
{
if(h[fi.to]==h[se.to])return fi.data<se.data;
return h[fi.to]>h[se.to];
}
void Tarjan(int x)
{
dfn[x]=1;
int len=a[x].size();
for(int i=0;i<len;i++)if(!dfn[a[x][i].first])Tarjan(a[x][i].first);
}
inline int afind(int x)
{
if(x==f[x])return x;
return f[x]=afind(f[x]);
}
int krus()
{
int num=0;
sort(d+1,d+1+cnt,cmp);
for(int i=1;i<=cnt;i++)
{
int u=afind(d[i].from),v=afind(d[i].to),w=d[i].data;
if(u==v)continue;
f[u]=v;
num+=w;
}
return num;
}
signed main()
{
//freopen("ski.in","r",stdin);
//freopen("ski.out","w",stdout);
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&h[i]),f[i]=i;
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%lld%lld%lld",&u,&v,&w);
if(h[u]>=h[v])a[u].push_back({v,w});
if(h[v]>=h[u])a[v].push_back({u,w});
}
Tarjan(1);
int ans1=0,ans2=0;
for(int i=1;i<=n;i++)
{
if(dfn[i])
{
ans1++;
int len=a[i].size();
for(int j=0;j<len;j++)d[++cnt]={i,a[i][j].first,a[i][j].second};
}
}
ans2=krus();
printf("%lld %lld",ans1,ans2);
return 0;
}
T4:
题意简述:
给定 \(n\) 个互不相交,可以重叠的矩阵,对某些点染色,这个点上的所有矩阵会被染上这个颜色,求最后每个矩阵会有多少种颜色。
解:
我们可以先把矩阵拆成上下两条水平线段,然后离线将染色与线段横坐标离散化,以纵坐标将矩阵将线段与染色一起处理。
维护一棵线段树,对于一个矩阵的下方线段加入,直接在线段树对应节点将其入栈,对于上方线段,直接在对应节点弹出栈顶即可,因为矩阵无相交,所以栈顶一定是需要弹出的线段。
对于染色操作,我们找到线段树对应的栈顶元素,用 set 记录一下。
由于若将大矩阵覆盖小矩阵看成大矩阵为小矩阵的父亲节点,那么不难发现这是棵森林,所以我们对于染色的节点,它们父亲也需要染这个颜色,这个就遍历一遍树用启发式合并即可。
时间复杂度:\(O(n\times \log(n)^2_2)\)。
#include<iostream>
#include<cstdio>
#include<vector>
#include<set>
#include<stack>
#include<algorithm>
using namespace std;
const int N=8e4+5;
int n,m,cnt,ans[N],cnp,ru[N],len1[12*N],len2[12*N];
set<int>num[N];
vector<int>b[N];
struct node
{
int name,x,y,h,k,flag;
}a[3*N];
struct node2
{
int name,data;
bool flag;
}t[3*N];
int cmp(node2 fi,node2 se)
{
return fi.data<se.data;
}
int cmp2(node fi,node se)
{
if(fi.h==se.h)return fi.flag<se.flag;
return fi.h<se.h;
}
vector<int>f[12*N];
inline int ls(int x)
{
return x<<1;
}
inline int rs(int x)
{
return x<<1|1;
}
void update(int x,int l,int r,int nl,int nr,int id,bool flag)
{
if(l>=nl&&r<=nr)
{
if(flag==0)
{
if(len1[x]==len2[x])f[x].push_back(id),len1[x]++,len2[x]++;
else f[x][len1[x]++]=id;
}
else len1[x]--;
return;
}
int mid=(l+r)>>1;
if(mid>=nl)update(ls(x),l,mid,nl,nr,id,flag);
if(mid<nr)update(rs(x),mid+1,r,nl,nr,id,flag);
}
int search(int x,int l,int r,int nl)
{
int ans=0;
if(len1[x]>0)ans=f[x][len1[x]-1];
if(l==r)return ans;
int mid=(l+r)>>1;
if(mid>=nl)ans=max(ans,search(ls(x),l,mid,nl));
else ans=max(ans,search(rs(x),mid+1,r,nl));
return ans;
}
void dfs(int x)
{
int len=b[x].size();
for(int i=0;i<len;i++)
{
dfs(b[x][i]);
if(num[b[x][i]].size()>num[x].size())swap(num[x],num[b[x][i]]);
for(set<int>::iterator j=num[b[x][i]].begin();j!=num[b[x][i]].end();j++)num[x].insert(*j);
}
ans[x]=num[x].size();
}
int main()
{
//freopen("plahteplahte.in","r",stdin);
//freopen("Plahte.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d%d",&a[i*2-1].x,&a[i*2-1].h,&a[i*2].y,&a[i*2].h);
a[i*2-1].name=a[i*2].name=i;
a[i*2-1].y=a[i*2].y;
a[i*2].x=a[i*2-1].x;
t[++cnp]={i,a[i*2-1].x,0};
t[++cnp]={i,a[i*2-1].y,1};
a[i*2-1].flag=0;
a[i*2].flag=2;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a[i+2*n].x,&a[i+2*n].h,&a[i+2*n].k);
t[++cnp]={i+2*n,a[i+2*n].x,0};
a[i+2*n].flag=1;
}
sort(t+1,t+1+cnp,cmp);
t[0].data=-1;
for(int i=1;i<=cnp;i++)
{
if(t[i].data!=t[i-1].data)cnt++;
if(t[i].flag==0)
{
if(t[i].name<=2*n)a[t[i].name*2-1].x=a[t[i].name*2].x=cnt;
else a[t[i].name].x=cnt;
}
else a[t[i].name*2-1].y=a[t[i].name*2].y=cnt;
}
sort(a+1,a+1+2*n+m,cmp2);
for(int i=1;i<=2*n+m;i++)
{
//cout<<a[i].name<<" "<<a[i].x<<" "<<a[i].y<<" "<<a[i].flag<<endl;
if(a[i].flag==0)
{
int x=search(1,1,cnt,a[i].x);
if(x)b[a[x].name].push_back(a[i].name),ru[a[i].name]++;
update(1,1,cnt,a[i].x,a[i].y,i,0);
}
else if(a[i].flag==1)
{
int x=search(1,1,cnt,a[i].x);
if(x)num[a[x].name].insert(a[i].k);
}
else update(1,1,cnt,a[i].x,a[i].y,i,1);
}
for(int i=1;i<=n;i++)if(!ru[i])dfs(i);
for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
return 0;
}

浙公网安备 33010602011771号