CSP-S模拟22
前言:
哈哈哈,又是一场爆零的模拟赛~~
\(T1:\) 木棍
思路:
机房出现了两种思路:
第一种: 我们不难发现,一共就只有五种情况\(\lbrace 334 \rbrace \lbrace 2233 \rbrace \lbrace 4222 \rbrace \lbrace 442 \rbrace \lbrace 22222 \rbrace\),再看一眼数据范围\(T≤100\),所以我们可以生成所有的排列。(应该是这样吧...)
第二种: 贪心。我们将\(3\)变为\(6\),然后贪心地选\(\lbrace 46 \rbrace \lbrace 226 \rbrace \lbrace 4222 \rbrace \lbrace 442 \rbrace \lbrace 22222 \rbrace\)就可以了。
代码(第二种的,丑):
$code$
#include<iostream>
#define int long long
using namespace std;
int T,n2,n3,n4,ans,ans1;
signed main(){
ios::sync_with_stdio(false);
cin>>T;
while(T--){
ans=0;
cin>>n2>>n3>>n4;
ans1=min(n3/2,n4);
ans+=ans1;
n3-=2*ans1;n4-=ans1;
ans1=min(n2,n4/2);
ans+=ans1;
n2-=ans1;n4-=2*ans1;
ans1=min(n3/2,n2/2);
ans+=ans1;
n3-=2*ans1;n2-=2*ans1;
ans1=min(n2/3,n4);
ans+=ans1;
n2-=3*ans1;n4-=ans1;
ans1=n2/5;
ans+=ans1;
cout<<ans<<'\n';
}
return 0;
}
\(T2:\) 环
思路:
我们首先将\(a[]\)数组进行排序,正确性是显然的(这地方没法说,“悠然心会,妙处难于君说”,自己手模一下就差不多了懂了)。然后,我们将会得到一个非常可观的性质:前面能选的后面一定能选。就此,我们可以开始考虑\(dp\)了。设\(dp_{i,j}\)表示左边的前\(i\)个点和右边的前\(a_i\)个点种有\(j\)单点和链。然后酱酱,酿酿,再酱酱酿酿就合并好了(其实是空口不好讲,一会儿代码见~~)。我是拿组合数写的,据说还可以用背包的思想把组合数拆了写,还可以滚动数组,不造,不费。
代码:
$code$
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
const int N=5200,mod=998244353,inv2=499122177;
int n,a[N],fac[N],inv[N],f[N][N];
inline int qpow(int x,int y){
int res=1;
while(y){
if(y&1) res=(res*x)%mod;
x=(x*x)%mod;
y>>=1;
}return res;
}//快速幂
inline int C(int n,int m){return fac[n]*inv[m]%mod*inv[n-m]%mod;}//组合数
signed main(){
ios::sync_with_stdio(false);
cin>>n;
//预处理组合数
fac[0]=inv[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
inv[n]=qpow(fac[n],mod-2);
for(int i=n-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
//排序
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
//递推方程式
int ans=0;f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=a[i-1];j++){
for(int k=j;k<=a[i];k++){
if(k-j>a[i]-a[i-1]) break;//记得判边界 不然会TLE
f[i][k]=(f[i][k]+f[i-1][j]*C(a[i]-a[i-1],k-j)%mod)%mod;
// 上一个的答案继承下来 * 在左边新加入的a[i]-a[i-1]个点中选出k-j来连接
}
}
ans=(ans+(f[i][1]-a[i]+mod)%mod)%mod;//要减去两个顶点的链,那是不符合条件的
for(int j=0;j<=a[i];j++) f[i][j]=(f[i][j]+f[i][j+1]*(j+1)%mod*j%mod)%mod;//将所有链强制定向合并
}
ans=ans*inv2%mod;//因为一个环顺时针逆时针算了两次,所以要除以2
cout<<ans%mod;
return 0;
}
\(T3:\) 传令
思路:
整体思路不难。我们发现直接求解很艰难,那我们不妨二分一下答案,然后\(check\)一下当前答案是否符合题意即可,具体实现细节见代码啊~~
代码:
$code$
#include<iostream>
#include<cmath>
#define int long long
using namespace std;
const int N=2e5+5;
int n,k,u,v,d,num,cnt,head[N],dis[N];
struct node{int to,nxt;}e[N<<1];
inline void add(int u,int v){
e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;
e[++cnt].to=u;e[cnt].nxt=head[v];head[v]=cnt;
}//建边
inline void dfs(int x,int fa){
dis[x]=1;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(y==fa) continue;
dfs(y,x);
dis[x]=min(dis[x],dis[y]+1);//求出x到子树内被直接通知的城市的最小距离
}
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(y==fa) continue;
if(dis[x]+dis[y]<=0) continue;//满足条件
dis[x]=max(dis[x],dis[y]+1);//求出x到子树内被直接通知的城市的最大距离
}
if(dis[x]>d) dis[x]=-d,num++;//当前点是否必须被选中
}
inline bool check(int x){
d=x;num=0;
dfs(1,0);
if(dis[1]>=1) num++;
return num<=k;//是否满足条件
}
signed main(){
ios::sync_with_stdio(false);
cin>>n>>k;
for(int i=1;i<n;i++){
cin>>u>>v;
add(u,v);//建边
}int l=1,r=n;
while(l<r){//二分答案取最小
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}cout<<l<<'\n';
return 0;
}
\(T4:\) 序列
思路:
虽然它看上去很困难的样子,但是经过机房大佬的激情演讲,我发现它还是很困难。然后,终于,在我不懈的努力下,我选择放弃了这道题。根本就看不懂,去找别的学长博客看去吧~~(不过他们今天好像都还没写??我不造啊)