CSP 模拟 5
T1 第一题
贪心,观察肯定是从较浅的点上来一个士兵或者从根节点来一个士兵,用 set 或者 vector 启发式合并维护这个过程即可
点击查看代码
#include<bits/stdc++.h>
#define N 100005
#define inf 0x3f3f3f3f
#define pii pair<int,int>
#define mp make_pair
using namespace std;
int n,head[N],tot,dep[N],mdep[N],ans;
vector<int> qwq[N];
struct edge{
int v,nxt;
}e[N*2];
inline void add(int u,int v){
e[++tot].v=v;
e[tot].nxt=head[u];
head[u]=tot;
}
void dfs(int u,int fa){
dep[u]=dep[fa]+1;mdep[u]=dep[u];
bool tmp=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(v==fa) continue;
dfs(v,u);tmp=0;
mdep[u]=max(mdep[u],mdep[v]);
for(int j:qwq[v]) qwq[u].push_back(j);
}
if(tmp){
qwq[u].push_back(dep[u]);
ans+=dep[u];return;
}
sort(qwq[u].begin(),qwq[u].end(),greater<int>());
int cnt=0;
for(int i=qwq[u].size()-1;i>=1;i--){
if(qwq[u][i]-dep[u]*2>=0) break;
ans+=qwq[u][i]-dep[u]*2;cnt++;
}
while((cnt--)&&qwq[u].size()) qwq[u].pop_back();
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dep[0]=-1;dfs(1,0);
printf("%d",ans);
}
T2 第二题
这个答案具有单调性显然,考虑决定答案瓶颈的数即可
二分答案,每次用类似 spfa 的方式,首先把所有数都压进一个队列中,每次将队首的数和周围的 6 个数的差值补到 \(mid\),如果一个节点被更新则压入队列继续更新,直到队列为空,然后比较操作次数和 \(k\) 的大小即可
点击查看代码
#include<bits/stdc++.h>
#define N 100005
#define pii pair<int,int>
#define mp make_pair
#define int long long
#define inf 0x3f3f3f3f
using namespace std;
int r,c,k,maxn,minn=inf,sum,vis[N];
int mvx[6]={0,0,-1,-1,1,1},mxy[6]={-1,1,0,1,-1,0};
vector<int> a[N],b[N];
inline int id(int x,int y){return c*(x-1)+y;}
bool check(int mid){
queue<pii> q;
memset(vis,1,sizeof(vis));
for(int i=1;i<=r;i++){
b[i]=a[i];
for(int j=1;j<=c;j++)
q.push(mp(i,j));
}
int tmp=0;
while(!q.empty()){
int x=q.front().first,y=q.front().second;
q.pop();vis[id(x,y)]=false;
int Maxn=0;
for(int i=0;i<6;i++){
int tx=x+mvx[i],ty=y+mxy[i];
if(tx<1||tx>r||ty<1||ty>c) continue;
Maxn=max(Maxn,b[tx][ty]);
}
if(Maxn-b[x][y]>mid){
tmp+=Maxn-b[x][y]-mid;
b[x][y]=Maxn-mid;
}
for(int i=0;i<6;i++){
int tx=x+mvx[i],ty=y+mxy[i];
if(tx<1||tx>r||ty<1||ty>c) continue;
if(abs(b[x][y]-b[tx][ty])>mid){
tmp+=b[x][y]-b[tx][ty]-mid;
b[tx][ty]=b[x][y]-mid;
if(!vis[id(tx,ty)]){
q.push(mp(tx,ty));
vis[id(tx,ty)]=true;
}
}
}
if(tmp>k) return false;
}
return true;
}
signed main(){
// srand(time(0)^*(new unsigned int));
cin>>r>>c>>k;
for(int i=1;i<=r;i++){
a[i].push_back(0);
for(int j=1;j<=c;j++){
int x;scanf("%lld",&x);
maxn=max(maxn,x);minn=min(minn,x);
a[i].push_back(x);
}
}
int l=0,r=maxn-minn,ans=maxn-minn;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)) r=mid-1,ans=mid;
else l=mid+1;
}
printf("%lld\n",ans);
}
T3 第三题
数位 dp,通常的数位 dp 为考虑是否压住上界,这个是考虑是否压住上下界
设状态 \(f_{i,j,0/1,0/1}\) 为选到第 \(i\) 位,选了 \(j\) 个 0,是否压住上下界,统计 \(a-1\sim \infty\) 和 \(b\sim \infty\) 的方案数相减即可
点击查看代码
代码
第四题
将 \(k^2\) 拆成 \(2\binom k2 +k\),就变为统计值都为 \(x\) 的点对数乘 2 和值为 \(x\) 的点数
设 \(f_{i,j}\) 为选到第 \(i\) 个位置,最大值为 \(j\) 的方案数,\(g_{i,j}\) 为从 \(i\) 位置,最大值为 \(j\) 的情况下选到末尾的方案数
则 \(i\) 位置对 \(x\) 的贡献为:
\[\sum_{y\ge x} f_{i-1,y}(g_{i+1,j}+2(n-i)g_{i+2,j})\\+f_{i-1,x-1}(g_{i+1,x}+2(n-i)g_{i+2,x})
\]
即为将 \(x\) 填到 \(i\) 位置,再考虑将前边和后边合起来,考虑对后边出现次数和点对的影响
点击查看代码
#include<bits/stdc++.h>
#define N 3005
#define int long long
using namespace std;
int n,m,ans[N];
int f[N][N],g[N][N],sum[N][N];
signed main(){
cin>>n>>m;
f[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
f[i][j]=(f[i-1][j]*j+f[i-1][j-1])%m;
for(int i=1;i<=n;i++) g[n+1][i]=1;
for(int i=n;i>=1;i--)
for(int j=n;j>=1;j--)
g[i][j]=(g[i+1][j]*j+g[i+1][j+1])%m;
for(int i=1;i<=n;i++)
for(int j=n;j>=1;j--)
sum[i][j]=(sum[i][j+1]+f[i-1][j]*(g[i+1][j]+2*(n-i)%m*g[i+2][j]%m))%m;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
ans[j]=(ans[j]+sum[i][j]+f[i-1][j-1]*(g[i+1][j]+2*(n-i)%m*g[i+2][j]%m)%m)%m;
for(int i=1;i<=n;i++)
printf("%lld ",ans[i]);
}