8.15 模拟赛小结
前言
最自闭的一集
T1 这一切
题意:给你一个图,定义操作为 若一个点只有一条白边 那么剩余的白边就会自动变黑 求至少要提前染几条边 通过若干次操作后使所有边都变黑
思考:画一下图就知道 满足所有点只有一条出边 其实就是一棵树 所以将每个联通快多余的边删掉即可
Code
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n,m,ans;
int head[N],tot=1;
struct edge{
int to,next;
}e[N*4];
void add(int u,int v)
{
e[tot]=(edge){v,head[u]};
head[u]=tot++;
}
int vis[N],cnt,p;
void dfs(int now)
{
p++;
vis[now]=1;
for(int i=head[now];i;i=e[i].next)
{
int to=e[i].to;
cnt++;
if(!vis[to]) dfs(to);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++)
if(!vis[i]) cnt=0,p=0,dfs(i),ans+=cnt/2-p+1;
cout<<ans;
return 0;
}
T2 都是 原题
题意:你可以随意交换数列两个数 使得最后的数列成为单峰数列 即存在一个 \(a_x\) 满足:
- \(i<k\) \(a_i>a_{i-1}\)
- \(i<k\) \(a_i<a_{i-1}\)
e.g
1 1 4 5 4 1
考场上是问了同学的 一开始一直在想逆序对
其实不妨这样思考:
从 \(1\) 开始考虑 \(1\) 必须扔到最前面或者最后面 扔完就变成了一个子问题
这样一想这问题就变成一个弟弟题了
所以往前找到有几个数和往后找到有几个数 哪里少就往哪里扔就行
树状数组维护即可
Code
#include<bits/stdc++.h>
#define ll long long
#define N 300005
using namespace std;
int n,a[N];
ll ans;
vector <int> pos[N];
struct tree{
ll tr[N];
void add(int x,int v)
{
while(x<=n)
{
tr[x]+=v;
x+=x&-x;
}
}
ll ask(int x)
{
ll sum=0;
while(x)
{
sum+=tr[x];
x-=x&-x;
}
return sum;
}
}tr;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
pos[x].push_back(i);
tr.add(i,1);
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<pos[i].size();j++) tr.add(pos[i][j],-1);
for(int j=0;j<pos[i].size();j++)
{
int x=pos[i][j];
ans+=min(tr.ask(x-1),tr.ask(n)-tr.ask(x));
}
}
cout<<ans;
return 0;
}
T3
呜呜呜我好菜写了一天
题意:有 \(n\) 个笼子 每个笼子可能会有动物 \(A,B,C\) \(A\) 能吃 \(B\),\(B\) 能吃 \(C\),\(C\) 能吃 \(A\).明显最初有 \(3^n\) 种状态
我好菜 不会在线算法 被吊打了
想想在线怎么做
考虑两个并查集 \(u,v\) 要合并到 \(u\)
那么 \(u\) 子树所有胜利状态应该乘上 \(\frac{2}{3}\) \(v\) 子树所有节点胜利状态应该乘上 \(\frac{1}{3}\) 这是题目给定的胜利概率 然后两棵树再并起来
然后因为并查集动态搞我太菜了不会 所以只能离线把原树拍到树上 dfs
序解决
时间复杂度还带个 \(log\) 我太菜了
#include<bits/stdc++.h>
#define ll long long
#define N 400005
using namespace std;
ll mod=998244353,inv[5],pw=1;
int n,m;
int trs[N][2],fa[N],cnt,pre[N];
struct prob{
int opr,u,v;
}op[N];
int in[N],out[N],dfn;
void dfs(int now)
{
if(!now) return;
in[now]=++dfn;
dfs(trs[now][0]);
dfs(trs[now][1]);
out[now]=dfn;
}
int tr[N];
void add(int x,ll v)
{
while(x<=dfn)
{
tr[x]=tr[x]*v%mod;
x+=x&-x;
}
}
ll ask(int x)
{
ll sum=1;
while(x)
{
sum=tr[x]*sum%mod;
x-=x&-x;
}
return sum;
}
int main()
{
inv[2]=(mod-mod/2);
inv[3]=(mod-mod/3)*inv[2]%mod;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
pre[i]=i,pw=pw*3%mod;
cnt=n;
for(int i=1;i<=m;i++)
{
scanf("%d",&op[i].opr);
if(op[i].opr==1)
{
scanf("%d%d",&op[i].u,&op[i].v);
int node=++cnt;
fa[pre[op[i].u]]=node,fa[pre[op[i].v]]=node;
trs[node][0]=pre[op[i].u],trs[node][1]=pre[op[i].v];
pre[op[i].u]=node;
}
else
scanf("%d",&op[i].u);
}
for(int i=1;i<=cnt;i++)
{
if(fa[i]==0) dfs(i);
}
for(int i=1;i<=n;i++)
pre[i]=i;
for(int i=1;i<=dfn;i++)
tr[i]=1;
add(1,pw);
for(int i=1;i<=m;i++)
{
if(op[i].opr==1)
{
int u=pre[op[i].u],v=pre[op[i].v];
add(in[u],inv[3]*2ll%mod),add(out[u]+1,3ll*inv[2]%mod);
add(in[v],inv[3]),add(out[v]+1,3);
pre[op[i].u]=fa[u];
}
else
printf("%lld\n",ask(in[op[i].u]));
}
return 0;
}
T4 选择
数据范围: \(n,q\leq 10^5\space k\leq L\leq200\)
这就是一个√8题目 错解跑的比正解快
题目建议:退背包
题目大意:有一个树 每次选点 \(u,v\) 作为一条路径的起点和终点 然后选 \(k\) 条路径 要求 \(k\) 条路径都必须且只能经过这条路径 求方案数
思路:容易发现 一个点合法方案数就是不经过父亲的边的其它子树中选一个点即可 如果取两个就会有交集 所以这样子很容易做个 dp 就行了 用 \(f_i\) 表示选了 \(i\) 个子树 然后记得要预处理一下全部选根的情况 就是 \(1\) 容易得到 \(O(nL^2)\) 的代码 因为它是有顺序取的 所以要乘上排列数
最离谱的是这个比正解快 只 T 一个点
但是正解 T 了我四个点 无语
95pts
代码
#include<bits/stdc++.h>
#define N 100005
#define ll long long
using namespace std;
ll mod=998244353;
int n,m,t;
//
int head[N],tot=1;
struct edge{
int to,next;
}e[N*2];
void add(int u,int v)
{
e[tot]=(edge){v,head[u]};
head[u]=tot++;
}
//f[i] 表示选了i次根的情况
int in[N],out[N],dfn,size[N],f[N];
void dfs(int now,int fa)
{
in[now]=++dfn;
size[now]=1;
for(int i=head[now];i;i=e[i].next)
{
int son=e[i].to;
if(son==fa) continue;
dfs(son,now);
size[now]+=size[son];
}
out[now]=dfn;
}
ll solve(int u,int v,int w)
{
for(int i=1;i<=w;i++)
f[i]=1;
int t=0,fa=0;
f[0]=f[1]=1;
for(int i=head[u];i;i=e[i].next)
{
int son=e[i].to,siz=size[son];
if(siz>size[u])
{
fa=u;
siz=n-size[u];
continue;
}
else
if(in[son]<=in[v]&&in[v]<=out[son])
{
t=1;
continue;
}
for(int j=w;j>=1;j--)
f[j]=(f[j]+f[j-1]*1ll*siz%mod*j%mod)%mod;
}
if(t==1)
for(int j=w;j>=1;j--)
f[j]=(f[j]+f[j-1]*1ll*(n-size[u])%mod*j%mod)%mod;
return f[w];
}
int main()
{
scanf("%d%d%d",&n,&m,&t);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs(1,0);
while(m--)
{
int u,v,k;
scanf("%d%d%d",&u,&v,&k);
printf("%lld\n",solve(u,v,k)*solve(v,u,k)%mod);
}
return 0;
}
然后正解
发现每次都做一次背包很多余 考虑退背包
每次求的是
\(f_i=f_i+f_{i-1}\times size \times i\)
如果去掉一个数 怎么办?
假设答案是 \(g\) 数组 已知 \(size\)
要退其实很简单 因为 \(f_0=g_0=1\)
然后一次次从 \(0\) 到 \(L\) 往前推就行
详细:
\(g_i=f_i+f_{i-1}\times size \times i\)
\(f_i=g_i-f_{i-1}\times size \times i\)
递推 \(f\) 即可
吐了不知道为什么 T 四个点 无语
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int mod=998244353;
int n,m,w;
int f[N][505];
//
int head[N],tot=1;
struct edge{
int to,next;
}e[N*2];
void add(int u,int v)
{
e[tot]=(edge){v,head[u]};
head[u]=tot++;
}
//f[i] 表示选了i次根的情况
int in[N],out[N],dfn,size[N];
void dfs(int now,int fa)
{
in[now]=++dfn;
size[now]=1;
for(int i=0;i<=w;i++)
f[now][i]=1;
int s=0;
for(int i=head[now];i;i=e[i].next)
{
int son=e[i].to;
if(son==fa) continue;
dfs(son,now);
size[now]+=size[son];
for(int j=w;j>=1;j--)
f[now][j]=(f[now][j]+1ll*f[now][j-1]*size[son]*j%mod)%mod;
}
for(int j=w;j>=1;j--)
f[now][j]=(f[now][j]+1ll*f[now][j-1]*(n-size[now])*j%mod)%mod;
out[now]=dfn;
}
int solve(int u,int v,int w)
{
int t=0,fa=0,siz=0;
for(int i=head[u];i;i=e[i].next)
{
int son=e[i].to;
if(size[son]>size[u])
{
fa=u;
continue;
}
if(in[son]<=in[v]&&in[v]<=out[son])
{
t=son;
break;
}
}
if(t==0) siz=n-size[u];
else siz=size[t];
int tmp=1;
// for(int j=0;j<=w;j++) g[j]=f[u][j];
for(int j=1;j<=w;j++) tmp=(f[u][j]-tmp*1ll*siz*j%mod+mod)%mod;
return tmp;
}
int main()
{
scanf("%d%d%d",&n,&m,&w);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs(1,0);
while(m--)
{
int u,v,k;
scanf("%d%d%d",&u,&v,&k);
printf("%d\n",1ll*solve(u,v,k)*solve(v,u,k)%mod);
}
return 0;
}