NOIP提高组模拟赛20
A. 星际旅行
乍一看好像挺难,考场上跳过做后面,回来一想也不是多难
大胆猜测结论,然后居然证出来了,然而没有开我直接爆炸
考场乱画发现猜测两边必须相邻,然后尝试证明
发现:一条边走两次两条边,走一次删一条边
然后问题就变成删去两条边,剩下的构成欧拉路
删去之前所有点的度数都是偶数,删去一条边会使两个点奇偶性改变,如果存在欧拉路,那么至多存在两个点度数为奇数,那么删去的两条边必然有一个公共点,才能满足性质
两个注意
一是自环,选择一个自环后所有点的度数仍为偶数,删去任何一条边都满足,所以自环需要特判
二是图可能不连通,这里的不联通是边不联通,与点无关,从有边的任意点开始DFS,经过点打个记号,最后判一下所有边连接的点是否都有记号即可
三是记得开
code
#include <cstring>
#include <cstdio>
using namespace std;
const long long maxn=100005;
struct edge{
long long to,net;
}e[maxn<<1|1];
long long d[maxn],tot=1,zh,head[maxn];
long long n,m;
bool f[maxn];
void add(long long u,long long v){
e[++tot].net=head[u];
head[u]=tot;
e[tot].to=v;
}
void DFS(long long x){
f[x]=1;
for(long long i=head[x];i;i=e[i].net){
long long v=e[i].to;
if(f[v])continue;
DFS(v);
}
}
long long work(){
DFS(e[tot].to);
for(long long i=2;i<=tot;i+=2)
if(f[e[i].to]==0||f[e[i^1].to]==0)return 0;
long long cnt=m,ans=0;
for(long long i=1;i<=zh;++i){
ans+=cnt-1;
--cnt;
}
for(long long i=2;i<=tot;i+=2){
if(e[i].to==e[i^1].to)continue;
--d[e[i].to];--d[e[i^1].to];
ans+=d[e[i].to]+d[e[i^1].to];
}
return ans;
}
int main(){
scanf("%lld%lld",&n,&m);
for(long long i=1;i<=m;++i){
long long u,v;scanf("%lld%lld",&u,&v);
add(u,v);add(v,u);
if(u!=v)++d[u],++d[v];
else ++zh;
}
printf("%lld\n",work());
return 0;
}
B. 砍树
考场先打的这题,以为就是个二分水题,虽然复杂度过于离谱但是没有细想,交题跑路,不过还好拿到
二分没啥说的,正解一会再讲,先看看有意思的
由于昨天有题需要随机打乱,改题时候我突发奇想加了个,结果发现了之前没有的点了没有的点
发现问题在于有的时候二分右界不应该缩的时候缩了,那么令右界有概率的缩呢?
else if(rand()%13==0)r=mid;
继续减小概率
else if(rand()%233==0)r=mid;
啪的一下,很快啊
WC,随机化有搞头,于是我成功带偏机房,都开始尝试rand
我尝试继续然后
其实还是失败了,因为最后A的那个有面向数据
if(check(9297884))l=9297884;
不过这个启发我们不会正解就乱搞
然后让我们谈谈正解
题目其实是求
设
原式
然后发现取值有限,当他的值确定下来,左边的显然越大越好,然后可以数论分块
code
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn=105;
ll a[maxn],n,k;
int main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
for(int i=1;i<=n;++i)k+=a[i];
ll ans=1;
for(ll l=1,r;l<=k;l=r+1){
r=k/(k/l);
ll sum=0;
for(int i=1;i<=n;++i){
sum+=a[i]/r;
if(a[i]%r)++sum;
}
if(sum*r<=k)ans=r;
}
printf("%lld\n",ans);
return 0;
}
附+二分代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=105;
ll l=1,r=1e11+1e9+3;
ll a[maxn],n,k;
ll ls[maxn];
bool check(ll x){
for(int i=1;i<=n;++i)ls[i]=a[i]%x;
ll sum=0;
for(int i=1;i<=n;++i)if(ls[i])sum+=x-ls[i];
if(sum>k)return false;
return true;
}
ll work(){
while(l<r){
if(r-l<=5){
for(ll i=r;i>=l;--i)if(check(i))return i;
}
ll mid=rand()%(r-l+1)+l;
if(check(mid))l=mid;
else if(rand()%233==0)r=mid;
}
return l;
}
int main(){
srand(time(NULL));
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
printf("%lld\n",work());
return 0;
}
C. 超级树
这几天打数论打傻了,看到读入就想排列组合,。。。。想偏了也没来得及打
正解,一个定义就很诡异的
表示一棵超级树中,有条不经过相同点的路径 的方案数
转移时候用类似拼接的思想超级树=两个超级树+1个节点
既然是拼接,刷表法会省不少事
考虑和能更新哪些位置
设
考虑新加入的点
- 不选
- 选,单独成一条路径
- 选,且放入左/右子树的一条路径中,可以放在路径的两个端点(*2),选择任意一条路径(l+r)
- 选,连接左右子树,先左后右先右后左不同(2),路径选择任意(lr)
- 选,连接左子树两条路径,或者右子树两条路径
完了,代码非常简单,但是就是想不到。。。。
我太菜了
code
#include<cstdio>
using namespace std;
typedef long long ll;
ll n,mod;
ll dp[305][605];
int main(){
scanf("%lld%lld",&n,&mod);
dp[1][0]=dp[1][1]=1%mod;
for(int i=1;i<n;++i){
for(int l=0;l<=n;++l){
for(int r=0;r<=n;++r){
ll sum=dp[i][l]*dp[i][r]%mod;
dp[i+1][l+r]+=sum;
dp[i+1][l+r+1]+=sum;
dp[i+1][l+r]+=2*sum*(l+r);
dp[i+1][l+r-1]+=sum*l*r*2;
dp[i+1][l+r-1]+=sum*(l*(l-1)+r*(r-1));
dp[i+1][l+r-1]%=mod;
dp[i+1][l+r+1]%=mod;
dp[i+1][l+r]%=mod;
}
}
}
printf("%lld\n",dp[n][1]%mod);
return 0;
}
D. 求和
本场水题,乍一看什么k次方,我就回忆起了被斯特林反演支配的恐惧(现在还是不会)
然后仔细一看,这玩意打个表不就行了?
果断切掉
code
#include <cstring>
#include <cstdio>
using namespace std;
const int mod=998244353;
const int maxn=300005;
typedef long long ll;
struct edge{
int net,to;
}e[maxn<<1|1];
int head[maxn],tot;
void add(int u,int v){
e[++tot].net=head[u];
head[u]=tot;
e[tot].to=v;
}
int rem[maxn][53],fa[maxn][23],dep[maxn],md;
void DFS(int x){
md=md>dep[x]?md:dep[x];
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;
if(v==fa[x][0])continue;
dep[v]=dep[x]+1;
fa[v][0]=x;
DFS(v);
}
}
int LCA(int u,int v){
if(dep[v]>dep[u]){
u^=v;v^=u;u^=v;
}
for(int i=20;i>=0;--i)if(dep[u]-dep[v]>=(1<<i))u=fa[u][i];
if(u==v)return u;
for(int i=20;i>=0;--i)if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int main(){
int n;scanf("%d",&n);
for(int i=1;i<n;++i){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dep[1]=0;DFS(1);
for(int i=1;i<=md;++i)rem[i][1]=i;
for(int i=1;i<=md;++i)
for(int j=2;j<=50;++j)
rem[i][j]=1ll*rem[i][j-1]*i%mod;
for(int j=1;j<=50;++j)
for(int i=1;i<=md;++i)
rem[i][j]=(1ll*rem[i][j]+rem[i-1][j])%mod;
for(int j=1;j<=20;++j)
for(int i=1;i<=n;++i)
fa[i][j]=fa[fa[i][j-1]][j-1];
int m;scanf("%d",&m);
for(int i=1;i<=m;++i){
int u,v,k;scanf("%d%d%d",&u,&v,&k);
int lca=LCA(u,v);
ll ans=1ll*rem[dep[u]][k]+rem[dep[v]][k]-rem[dep[lca]][k];
if(dep[lca]-1>0)ans-=rem[dep[lca]-1][k];
ans=(ans%mod+mod)%mod;
printf("%lld\n",ans);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】