2023.8.3测试
一场
T1 跑路
一个
签到题,设
T2 Cleaning
一开始只会打特殊性质,最后
数据太水放我过了,但实际上在 At 过不了(细节出大锅)
对于节点
解得
显然有范围限制
对于在此处合并的,考虑存在方案使得可以凑出
特别地,当
注意特判
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=100010,M=400010,INF=1e9;
int T,n,rt,a[N],deg[N];
vector <int> g[N];
bool flag1,flag2;
bool dfs(int x,int fa)
{
if(deg[x]==1)
return 1;
LL sum=0,mx=0;
for(int i=0; i<g[x].size(); i++)
{
int y=g[x][i];
if(y==fa)
continue;
bool ansy=dfs(y,x);
if(!ansy)
return 0;
sum+=(LL)a[y];
mx=max(mx,(LL)a[y]);
}
if(sum<(LL)a[x])
return 0;
LL aa=sum-(LL)a[x],bb=1LL*2*a[x]-sum;
if(aa<0 || bb<0 || bb>a[x] || aa>a[x] || sum-mx<aa)
return 0;
if(x==rt && aa!=a[x])
return 0;
a[x]-=aa;
return 1;
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
for(int i=1; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
deg[x]++; deg[y]++;
g[x].push_back(y); g[y].push_back(x);
}
if(n==2)
{
if(a[1]==a[2])
printf("YES");
else
printf("NO");
return 0;
}
for(int i=1; i<=n; i++)
{
if(deg[i]!=1)
{
rt=i;
break;
}
}
bool pd=dfs(rt,0);
if(pd)
printf("YES");
else
printf("NO");
return 0;
}
T3 Two Faced Edges
很牛逼的题,只会暴力
先求一次
-
和 在同一个 里此时
数量若不改变,则需满足还存在一条 的路径不经过边 -
和 不在同一个 里此时
数量若改变,则同样需满足还存在一条 的路径,不经过边
设
从
时间复杂度
#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=200010;
int n,m;
int cnt,dfn[N],low[N],num,ins[N],c[N],sta[N],top;
int f[N][N],dep[N],cmp[N];
struct node{int x,y;}e[M];
vector <int> g[N];
void Tarjan(int x)
{
dfn[x]=low[x]=++num;
sta[++top]=x; ins[x]=1;
int len=g[x].size();
for(int i=0; i<len; i++)
{
int y=g[x][i];
if(!dfn[y])
{
Tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(ins[y])
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
int y; cnt++;
do
{
y=sta[top--];
ins[y]=0;
c[y]=cnt;
}while(x!=y);
}
}
void dfs1(int x,int d)
{
dep[x]=d;
int len=g[x].size();
for(int i=0; i<len; i++)
{
int y=g[x][i];
if(dep[y])
continue;
dfs1(y,d+1);
}
}
void dfs2(int x,int d)
{
dep[x]=d;
int len=g[x].size();
for(int i=len-1; i>=0; i--)
{
int y=g[x][i];
if(dep[y])
continue;
dfs2(y,d+1);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++)
{
scanf("%d%d",&e[i].x,&e[i].y);
g[e[i].x].push_back(e[i].y);
}
for(int i=1; i<=n; i++)
if(!dfn[i])
Tarjan(i);
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
dep[j]=0;
dfs1(i,1);
for(int j=1; j<=n; j++)
cmp[j]=dep[j],dep[j]=0;
dfs2(i,1);
for(int j=1; j<=n; j++)
{
if(cmp[j]==2 && dep[j]==2)
f[i][j]=0;
else
f[i][j]=1;
}
}
for(int i=1; i<=m; i++)
{
int x=e[i].x,y=e[i].y;
if(c[x]==c[y])
{
if(f[x][y])
printf("same\n");
else
printf("diff\n");
}
else
{
if(f[x][y])
printf("diff\n");
else
printf("same\n");
}
}
return 0;
}
T4 ColoringBalls
神仙题,不在能力范围之内
Step 1:考虑某种颜色序列能否被生成
注意到如果一些颜色段之间有黑色,那么这些颜色段之间就是相互独立的。于是我们先考虑这些独立的颜色段
一些叠加的等效的操作(如操作 rbr
染成一个纯红色段)其实都可以变成一个最简的操作(只操作 r
,略过 rb
),因此我们我们定义一个颜色序列
-
考虑杂色段的最简操作序列
不难发现,如果一个杂色段段有
个蓝色段,那么至少要染 次,红色和蓝色的染色次数在 之间,都至少染一次。特殊的,若没有蓝色,则只需染一次红色在此基础上,还有染色顺序的限制。若我们决定染
次蓝色,最好的方案是:先染 次红色,再染 次蓝色,然后染一大段蓝色,最后这段大蓝色染完剩下的红色因此对杂色段最简操作序列的要求:满足个数
,最前面是r
,第二个是b
; -
而纯红段的最简操作序列即是一个
r
接着考虑由黑球分隔的各个颜色段
-
设有
个杂色段,现在要在整个操作序列中选出若干个子序列,分别作为每个杂色段的操作序列。我们先要选出
个子序列{r,b}
作为 个最简操作序列的开头,之后要为每个最简操作序列添加指定数目的字符,且满足字符在开头{r,b}
的右侧。记
表示表示第 个{r,b}
右侧剩余的未选择的字符数目,对应的最简操作序列还需要 个字符。那贪心一下,显然我们要将 从大到小排序,依次考虑,每次要尽量靠左选择字符形式化的,有解的充要条件是
用人话说就是剩余的字符数目要够自己和后面的所有
{r,b}
选择进一步考虑如何排列
的顺序。不难发现,将 降序排列,每个 都取到最小值,此时最可能满足条件 -
再考虑
个纯红色段的情况,根据贪心,显然选取前面 个r
最优
Step 2: 计数
枚举纯红段和杂色段的数量
先考虑在确定
-
先考虑颜色段之间的排列顺序。首先是杂色段和纯红段混合的方案数,为
。由于纯红段最简操作序列的长度都是 ,因此我们只需考虑杂色段内部的顺序数,即 的不同排列数。这是典型的多重排列,记 ,排列数即为 -
接下来考虑确定颜色段顺序后的方案数
各个颜色段长度不定,可以看作装球的盒子
-
对于一个杂色段对应的
,(注意总字符数为 ,蓝色段个数为 ),会产生 个至少有一个球的颜色段,以及 个可以为空的颜色段(两端的红球段) -
对于一个纯红段,会产生
个至少有一个球的颜色段 -
对于黑色的部分,会产生
个至少有一个球的颜色段,以及 个可以为空的颜色段(两端的黑球段)
综上,非空盒的个数为
,可空盒的个数为 ,总球数为 ,插板法可求,答案为 -
接下来对不同的
设
式子
前文的
最终的答案为
使用前缀和优化
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=80,MOD=1e9+7;
int n,m;
int C[6*N][6*N],t[N],posr[N],posb[N];
LL ans,f[N][N][N];;
char str[N];
bool v[N];
void prework()
{
for(int i=0; i<=320; i++)
C[i][0]=1;
for(int i=1; i<=320; i++)
for(int j=1; j<=i; j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
}
LL calc(int a,int c)
{
memset(v,0,sizeof(v));
memset(f,0,sizeof(f));
int cnt=0; LL res=0;
for(int i=1; i<=m; i++) //计算杂色段的{r,b}
{
if(cnt==c)
break;
if(str[i]=='r')
{
v[i]=1;
posr[++cnt]=i; posb[cnt]=0;
for(int j=i+1; j<=m; j++)
{
if(str[j]=='b' && !v[j])
{
v[j]=1;
posb[cnt]=j;
break;
}
}
if(!posb[cnt])
return 0;
}
}
if(cnt<c)
return 0;
cnt=0;
for(int i=1; i<=m; i++) //计算纯红段的一个r
{
if(cnt==a)
break;
if(v[i])
continue;
if(str[i]=='r')
v[i]=1,cnt++;
}
if(cnt<a)
return 0;
cnt=0; int id=c;
for(int i=m; i>=1; i--) //计算t_1~t_c
{
if(!v[i])
cnt++;
if(i==posb[id])
t[id]=cnt,id--;
}
for(int i=0; i<=m; i++)
f[c+1][0][i]=1;
for(int i=c; i>=1; i--)
{
for(int j=0; j<=t[i] && 2*(j+a+c)-1<=n; j++)
{
for(int k=1; k<=m; k++)
{
int kk=k-1; //注意这里有一个下标的偏移
for(int p=1; p<=c-i+1 && j-p*kk>=0 && j-p*kk<=t[i+p]; p++)
(f[i][j][k]+=1LL*f[i+p][j-p*kk][k-1]*C[c-i+1][p]%MOD)%=MOD;
}
for(int k=1; k<=m; k++) //前缀和优化
(f[i][j][k]+=f[i][j][k-1])%=MOD;
}
}
for(int j=0; j<=m; j++)
{
int x=2*(j+a+c)-1,y=2*c+2;
(res+=1LL*f[1][j][m]*C[n+y-1][x+y-1]%MOD)%=MOD; //s序列的不同排列数
}
res=1LL*res*C[a+c][a]%MOD; //别忘了这个
return res;
}
int main()
{
prework();
scanf("%d%d%s",&n,&m,str+1);
for(int a=0; a<=m; a++) //枚举a,c
for(int c=0; a+2*c<=m; c++)
(ans+=calc(a,c))%=MOD;
printf("%lld",ans);
return 0;
}
总结
-
T1 切的有点慢,以后得加快速度
-
T2 最后时间才想出正解,需要加强思维。部分分要大胆写,这次正解就是在部分分里想出来的。写代码要注意细节与范围限制,不能写的太随便
-
T3 其实可以进一步思考,不能满足暴力
-
T4 随缘
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?