1. 程式人生 > 其它 >NOIP提高組模擬賽20

NOIP提高組模擬賽20

A. 星際旅行

乍一看好像挺難,考場上跳過做後面,回來一想也不是多難

大膽猜測結論,然後居然證出來了,然而沒有開\(long long\)我直接爆炸

考場亂畫發現猜測兩邊必須相鄰,然後嘗試證明

發現:一條邊走兩次\(=\)兩條邊,走一次\(=\)刪一條邊

然後問題就變成刪去兩條邊,剩下的構成尤拉路

刪去之前所有點的度數都是偶數,刪去一條邊會使兩個點奇偶性改變,如果存在尤拉路,那麼至多存在兩個點度數為奇數,那麼刪去的兩條邊必然有一個公共點,才能滿足性質

兩個注意

一是自環,選擇一個自環後所有點的度數仍為偶數,刪去任何一條邊都滿足,所以自環需要特判

二是圖可能不連通,這裡的不聯通是邊不聯通,與點無關,從有邊的任意點開始DFS,經過點打個記號,最後判一下所有邊連線的點是否都有記號即可

三是記得開\(longlong\)

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. 砍樹

考場先打的這題,以為就是個二分水題,雖然複雜度過於離譜但是沒有細想,交題跑路,不過還好拿到\(40pts\)

二分沒啥說的,正解一會再講,先看看有意思的

由於昨天有題需要隨機打亂,改題時候我突發奇想加了個\(rand\),結果發現\(A\)了之前沒有\(A\)的點\(WA\)了沒有\(WA\)的點

發現問題在於有的時候二分右界不應該縮的時候縮了,那麼令右界有概率的縮呢?

else if(rand()%13==0)r=mid;

\(40->50\)

繼續減小概率

else if(rand()%233==0)r=mid;

啪的一下,很快啊\(50->90\)

WC,隨機化有搞頭,於是我成功帶偏機房,都開始嘗試rand

我嘗試繼續\(rand\)然後

其實還是失敗了,因為最後A的那個有面向資料

if(check(9297884))l=9297884;

不過這個啟發我們不會正解就亂搞

然後讓我們談談正解

題目其實是求

\(\sum_{i-1}^n\lceil \frac{a_i}{d}\rceil*d-\sum_{i=1}^na_i<=k\)

\(C=\sum_{i=1}^na_i+k\)

原式\(\sum_{i-1}^n\lceil \frac{a_i}{d}\rceil<=\lfloor C/d\rfloor\)

然後發現\(\lfloor C/d\rfloor\)取值有限,當他的值確定下來,左邊的\(d\)顯然越大越好,然後可以數論分塊

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;
}

\(rand\)+二分\(90pts\)程式碼

$rand大法好$
#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. 超級樹

這幾天打數論打傻了,看到讀入\(mod\)

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;
}