思路来源
https://codeforces.com/blog/entry/61311(官方题解)
https://www.cnblogs.com/tobyw/p/9513876.html(E题)
D.Mouse Hunt
n(n<=2e5)个房间,有一只老鼠,可能在t=0时出现在任意房间,
第i个房间放捕鼠夹的代价是ci(1<=ci<=1e4),
在这一秒出现在i房间的老鼠下一秒出现的房间号是ai(1<=ai<=n)
问最小的放捕鼠夹代价和,使得老鼠出现在任意一个房间都能被捕
题解
从一个点出发,要么到下一个点,要么到自身
所以每个连通块要么是一个ρ字形,要么是一个环(含自环)
考虑到,环上必须放捕鼠夹,且只放在环上就能保证该连通块可捕鼠,
那么放在环上的最小cost房间上即可,
dfs统计每个连通块的环,将每个环上的最小值计入贡献
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
int n,a[maxn+2],c[maxn+6];
int cnt;//cnt个cycle
vector<int>cycle[maxn+5];
int vis[maxn+3];//为0未访问 为1在栈中 为2已访问
int path[maxn+4],tot,tmp;
int mn,len,v;
ll ans;
void dfs(int u)
{
path[++tot]=u;//记录当前路径,入栈
vis[u]=1;
int v=a[u];//实际不用建图,找后继即可
if(vis[v]!=2)
{
if(vis[v]==1)//发现环
{
cnt++;
tmp=tot;//只是记录 并不在记录环时退栈
while(path[tmp]!=v)cycle[cnt].push_back(path[tmp--]);//退栈记录环 其实就是tarjan
cycle[cnt].push_back(v);
}
else dfs(v);
}
--tot;//退栈
vis[u]=2;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&c[i]);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
for(int i=1;i<=n;++i)
if(!vis[i])dfs(i);
for(int i=1;i<=cnt;++i)
{
len=cycle[i].size();
mn=c[cycle[i][0]];//记录环上点pos 对应权值c[pos]
for(int j=0;j<len;++j)
{
v=cycle[i][j];
mn=min(mn,c[v]);
}
ans+=mn;
}
printf("%I64d\n",ans);
return 0;
}
E.Inverse Coloring
n*n(1<=n<=500)的方格板,每个方格可以涂黑或白色,
求满足下列条件的方案数:
①相邻行的颜色完全相同或完全相反
②相邻列的颜色完全相同或完全相反
③单一颜色的联通块长方形面积小于给定的k(1<=k<=n*n)
题解
用01序列来代表行的染色情况,那么有01对应黑白①和01对应白黑②两种,
如果行的01序列、列的01序列确定,就可以按数独一样,确定整张图
再在染色方法①、②中选一种,唯一确定这张图
把01序列转化成连续的计数,如011000转化为123,
那么根据坐标离散化,新的点的面积=离散化的行宽*离散化的列高,
要求每个矩形的面积都小于k,即最大值小于k,即行宽最大值*列高最大值<k(因为x行y列一定会有一个交叉点(x,y))
考虑和为n,最大值为m的方案数dp[n][m]:
该方案数可以由其子情况补上一个不大于m的数得来,可以补1...2...m
即dp[n][m]=dp[n-1][m]+(dp[n-2][m]+...+dp[n-m][m])③
类比有dp[n-1][m]=(dp[n-2][m]+...+dp[n-m][m])+dp[n-m-1][m]④
④代③有,dp[n][m]=2*dp[n-1][m]-dp[n-m-1][m]
前提是n-m-1>=0,即n>=m+1,这也意味着dp[0][m]得赋值为1,作为base情况
而n<=m的情况,意味着最大值大于等于n,即整数n任意划分
n个球n-1个空任意放挡板的方案数
注意到最大值具有前缀和的性质,即行max为m,是这一行的max不超过m,
统计这一行max恰为m的方案数,用max[m]-max[m-1]即可
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
const int maxn=500;
const int N=maxn+5;
//dp[i][j]表示和为i最大值为j的方案数
int n,k,dp[N][N];
int ans,now;
void add(int &x,int y)
{
x+=y;
if(x<0)x+=mod;
if(x>=mod)x-=mod;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=maxn;++i)
dp[0][i]=1;//底部条件 便于n-m-1==0
dp[1][1]=1;//dp[n][n]=2^(n-1) n个任取
for(int i=2;i<=maxn;++i)
dp[i][i]=(dp[i-1][i-1]*2)%mod;
for(int i=1;i<=maxn;++i)
{
for(int j=1;j<i;++j)
dp[i][j]=(2*dp[i-1][j]%mod-dp[i-j-1][j]%mod+mod)%mod;
for(int j=i+1;j<=maxn;++j)
dp[i][j]=dp[i][i];
}
for(int i=1;i<=n;++i)//枚举行max 由行max*列max<=k-1可得
{
now=dp[n][i]-dp[n][i-1];
now=1ll*now*dp[n][min((k-1)/i,n)]%mod;
add(ans,now);
}
printf("%d\n",(2*ans)%mod);
return 0;
}
F.Session in BSU
n(1<=n<=1e6)门考试,第i门考试有第一次考试时间第ai天和第二次考试时间第bi天(1<=ai<bi<=1e9)
考试可能存在冲突的天,问是否能通过所有门的考试,
若能,输出通过考试的最小天数;若不能输出-1
题解
瞎搞并查集搞过去了
离散化ai和bi到2e6的范围,建图连ai和bi,
考虑每个连通块,每条边都得选一个点作为归属,边是考试,点是第几天
对于孤立点,没有边,直接跳过(事实上由于离散化,不会有这样的点)
对于树,点比边多一个,可以弃掉最大的点,取次大值
对于基环树,点边相等,不可舍弃,取最大值
对于有大于等于两个环的连通块,不能满足所有边都被选的要求
并查集维护每个连通块的最大值、次大值、环的个数,统计每个块对答案贡献
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
struct node
{
int cyc,mx,se,fa,sz;
node():cyc(0){
}
}par[2*maxn];
int find(int x)
{
return par[x].fa==x?x:par[x].fa=find(par[x].fa);
}
int n,a[maxn],b[maxn];
int c[2*maxn],cnt;
int tmp[4],tot;
int x,y,ans;
bool flag;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d%d",&a[i],&b[i]);
c[++cnt]=a[i];
c[++cnt]=b[i];
}
sort(c+1,c+cnt+1);
cnt=unique(c+1,c+cnt+1)-(c+1);
for(int i=1;i<=n;++i)
{
a[i]=lower_bound(c+1,c+cnt+1,a[i])-c;
b[i]=lower_bound(c+1,c+cnt+1,b[i])-c;
//printf("a[]:%d b[]:%d\n",a[i],b[i]);
}
for(int i=1;i<2*maxn;++i)
{
par[i].sz=1;
par[i].fa=i;
par[i].mx=i;
par[i].se=-1;//不存在第二大
par[i].cyc=0;
}
for(int i=1;i<=n;++i)
{
x=find(a[i]),y=find(b[i]);
if(x==y)
{
par[x].cyc++;
//printf("time[%d]:cyc[%d]:%d\n",i,x,par[x].cyc);
continue;
}
if(par[x].sz<par[y].sz)swap(x,y);
tot=0;
tmp[tot++]=par[x].mx;tmp[tot++]=par[x].se;
tmp[tot++]=par[y].mx;tmp[tot++]=par[y].se;
sort(tmp,tmp+tot);
par[x].mx=tmp[3];
par[x].se=tmp[2];
par[x].sz+=par[y].sz;
par[x].cyc+=par[y].cyc;
par[y].fa=x;
}
for(int i=1;i<2*maxn;++i)
{
if(par[i].fa==i&&par[i].sz>1)
{
//printf("%d:%d %d %d %d\n",i,par[i].sz,par[i].mx,par[i].se,par[i].cyc);
if(par[i].cyc>1)flag=1;
else if(par[i].cyc==1)ans=max(ans,c[par[i].mx]);
else ans=max(ans,c[par[i].se]);
}
}
if(!flag)printf("%d\n",ans);
else puts("-1");
return 0;
}