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