NOIP模擬92&93(多校26&27)

Varuxn發表於2021-11-11

前言

由於太菜了,多校26 只改出來了 T1 ,於是直接並在一起寫啦~~~。

T0 NOIP 2018

解題思路

第一次考場上面寫三分,然而我並不知道三分無法處理不是嚴格單峰的情況,但凡有一個平臺都不行??

我們的貪心策略一定是儘量讓我們選擇的兩個物品的值儘量接近,二分最後選擇的一個價值,然後判斷是否可行。

需要特判一下只選擇一個的情況,以及兩個個數相差一的情況,實現細節有一點多。。

code

#include<bits/stdc++.h>
#define int __int128
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void write(int x)
{
	#define sta number
	int sta[70],top=0; if(x<0) putchar('-'),x=~(x-1);
	while(x) sta[++top]=x%10,x/=10; if(!top) sta[++top]=0;
	while(top) putchar(sta[top--]+'0'); putchar('\n');
	#undef sta
}
int q,n,a,b,c,d,ans;
inline int g(int x,int y,int pos){return x*pos+y*(pos-1)*pos/2;}
inline long long g2(long long x,long long y,long long pos){return x*pos+y*(pos-1)*pos/2;}
inline int work(int x,int y){return (x-a)/b+(y-c)/d+2;}
bool check(int x)
{
	int t1=max((int)0,(x-a)/b+1),t2=max((int)0,(x-c)/d+1),temp=g(a,b,t1)+g(c,d,t2);
	if(temp<=n) return true;
	if(t1>=1&&g(a,b,t1-1)+g(c,d,t2)<=n) ans=max(ans,t1+t2-1);
	else if(t2>=1&&g(a,b,t1)+g(c,d,t2-1)<=n) ans=max(ans,t1+t2-1);
	return false;
}
void sol(int x,int y)
{
	int l=0,r=n,temp=-1;
	while(l<=r)
	{
		int mid=(l+r)>>1,jud=false;
		jud|=x*mid>n; jud|=g(x,y,mid)!=g2(x,y,mid);
		jud|=g(x,y,mid)>n;
		if(!jud) l=mid+1,temp=mid;
		else r=mid-1;
	}
	ans=max(ans,temp);
}
void solve()
{
	a=read(); b=read(); c=read(); d=read(); n=read();
	int l=0,r=n,temp=-1; ans=0; sol(a,b); sol(c,d);
	while(l<=r)
	{
		int mid=(l+r)>>1; 
		if(check(mid)) l=mid+1,temp=mid;
		else r=mid-1;
	}
	if(~temp) ans=max(ans,work(temp,temp)); write(ans);
}
#undef int
int main()
{
	#define int long long
	freopen("money.in","r",stdin); freopen("money.out","w",stdout);
	q=read(); while(q--) solve();
	return 0;
}

T1 開掛

解題思路

一個直接的想法就是給 \(b\) 排序然後給需要移動步數多的點較小的 \(b\)

我們肯定是要把一些重複的值填在一些空閒的值域裡面,那麼為了讓答案儘量小,我們要讓移動步數儘量不平均。

於是可以先把所有需要安排位置的數字放入一個棧,數值從棧低到棧頂單調不降。

對於一個空閒的值域,優先選擇棧頂元素也就是較大的元素放入就好了..

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e6+10,INF=1e18;
int n,p,cnt,all,top,sta[N],a[N],b[N],c[N],s[N];
pair<int,int> res[N];
ull ans;
#undef int
int main()
{
	#define int long long
	freopen("openhook.in","r",stdin); freopen("openhook.out","w",stdout);
	n=read(); a[n+1]=INF;
	for(int i=1;i<=n;i++) a[i]=read(); sort(a+1,a+n+1);
	for(int i=1;i<=n;i++) b[i]=read(); sort(b+1,b+n+1);
 	for(int i=1,len=1;i<=n;i++)
		if(a[i]==a[i+1]) s[++all]=a[i];
		else if(a[i+1]!=a[i]+1)res[++cnt]=make_pair(a[i]+1,a[i+1]-1);
	for(int i=1,pos=0;i<=cnt;i++)
	{
		if(s[pos]>res[i].second) continue;
		while(pos<all&&s[pos+1]<res[i].first) sta[++top]=s[++pos];
		for(int j=res[i].first;j<=res[i].second&&top;j++)
		{
			c[++p]=j-sta[top--];
			while(pos<all&&s[pos+1]<j+1) sta[++top]=s[++pos];
		}
	}
	sort(c+1,c+n+1,greater<int>());
	for(int i=1;i<=n;i++) ans+=c[i]*b[i];
	printf("%llu",ans);
	return 0;
}

T2 叄仟柒佰萬

解題思路

子段中 \(mex\) 的值一定是全域性的 \(mex\) 值。

\(f_i\) 表示前 \(i\) 的數字劃分子段 \(mex\) 等於 \(1\sim n\)\(mex\) 值的方案數。

假設全域性 \(mex\) 值為 \(K\) ,那麼轉移方程就是 \(f_i=\sum\limits_{j=1}^{i-1}f_{j-1}\times[mex_{[i,j]}=K]\)

我們可以維護一個指標 \(pos\) 表示 \([1,pos]\)\(mex\) 值都是 \(K\) 這個指標顯然只會右移,同時在維護一下當前 \([pos,i]\) 區間的 \(mex\) 值用來維護。

實現細節稍多。

code

#include<bits/stdc++.h>
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=37e6+10,Num=262143,mod=1e9+7;
int T,n,pos,num,ans,X,Y,s[N],pre[N],cnt[N],mex;
bool vis[N];
void solve()
{
	n=read(); ans=vis[0]=0; pos=num=mex=0; pre[0]=1; s[0]=N-1;
	if(n!=N-10) for(int i=1;i<=n;i++) s[i]=read(),vis[i]=false;
	else{X=read();Y=read();for(int i=2;i<=n;i++)s[i]=(1ll*s[i-1]*X+Y+i)&Num;}
	for(int i=1;i<=n;i++) vis[s[i]]=true; while(vis[mex]) mex++;
	if(n!=N-10) for(int i=0;i<=n;i++) cnt[i]=0;
	for(int i=1;i<=n;i++)
	{
		int las=-1; cnt[s[i]]++; while(cnt[num]) num++;
		while(num>=mex&&pos<=i){cnt[s[pos]]--; if(!cnt[s[pos]]&&s[pos]<num) las=num,num=s[pos]; pos++;}
		if(~las) pos--,cnt[s[pos]]++,num=las; pre[i]=pre[i-1]; if(pos>=1) pre[i]=(pre[i]+pre[pos-1])%mod;
	}
	printf("%d\n",(pre[n]-pre[n-1]+mod)%mod);
}
int main()
{
	freopen("clods.in","r",stdin); freopen("clods.out","w",stdout);
	T=read(); while(T--) solve();   
	return 0;
}

T3 超級加倍

解題思路

有一種 Kruskal 重構樹的感覺(好像從嚴格意義上來講並不是),更多的開始能使一種笛卡爾樹的東西吧。。

按照編號從大到小建樹為 T1 從小到大建樹為 T2 。

那麼這兩棵樹的性質就是: 滿足任意兩點 x, y 在 T1 中的 lca 是路徑最小值,在 T2 中是路徑最大值。

那麼符合條件的點對 \((x,y)\) 就需要滿足 x 在 T1 中是 y 的祖先,y 在 T2 中是 x 的祖先。

直接在一棵樹的 DFS 序上開樹狀陣列,然後 DFS 另一棵樹記錄祖先的資訊查詢就好了。

code

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=2e6+10;
int n,dfn[N],siz[N];
long long ans;
vector<int> v[N];
struct Edge
{
	int tot=1,tim,head[N],ver[N<<1],nxt[N<<1];
	void add(int x,int y){ver[++tot]=y;nxt[tot]=head[x];head[x]=tot;}
	void dfs(int x){dfn[x]=++tim;siz[x]=1;for(int i=head[x];i;i=nxt[i])dfs(ver[i]),siz[x]+=siz[ver[i]];}
}e1,e2;
struct DSU
{
	int fa[N];
	void init(){for(int i=1;i<=n;i++)fa[i]=i;}
	int find(int x){if(fa[x]==x) return x;return fa[x]=find(fa[x]);}
	void merge(int x,int y){fa[find(x)]=find(y);}
}D1,D2;
struct BIT
{
	int tre[N];
	#define lowbit(x) (x&(-x))
	void insert(int x,int val){for(int i=x;i<=n;i+=lowbit(i))tre[i]+=val;}
	int query(int x){int sum=0;for(int i=x;i;i-=lowbit(i))sum+=tre[i];return sum;}
	int query(int l,int r){return query(r)-query(l-1);}
}T;
void dfs(int x)
{
	ans+=T.query(dfn[x],dfn[x]+siz[x]-1); T.insert(dfn[x],1);
	for(int i=e2.head[x];i;i=e2.nxt[i]) dfs(e2.ver[i]);
	T.insert(dfn[x],-1);
}
int main()
{
	freopen("charity.in","r",stdin); freopen("charity.out","w",stdout);
	n=read(); read(); D1.init(); D2.init();
	for(int i=2,x;i<=n;i++) x=read(),v[x].push_back(i),v[i].push_back(x);
	for(int i=n;i>=1;i--) for(auto it:v[i]) if(it>i) e1.add(i,D1.find(it)),D1.merge(it,i);
	for(int i=1;i<=n;i++) for(auto it:v[i]) if(it<i) e2.add(i,D2.find(it)),D2.merge(it,i);
	e1.dfs(1); dfs(n); printf("%lld",ans);
	return 0;
}

T4 歡樂豆

解題思路

正解是 線段樹+最短路 ,但是貌似可以亂搞過。。

先把所有的關於 \(m\) 條邊所涉及到的點記錄下來,那麼別的點到其他點的距離就是初始權值。

同時我們還需要再記錄一下初始權值最小的點以保證我們再後面的最短路中對於附給大邊權的情況可以跑回來。

然後對於每一個記錄下來的點為源點跑最短路,一邊跑一遍用並茶几維護,具體實現可以再向優先佇列裡面加入元素的時候記錄一個 bool 值。

表示時候已經計算過了這個點到別的聯通塊的距離,就可以直接跑了。。。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e5+10,INF=1e18;
int n,m,mn=INF,mnid,ans,cnt,dis[N],sta[N],id[N],s[N],fa[N];
bool vis[N];
struct Node
{
	int pos,dat,can;
	bool friend operator < (Node x,Node y){return x.dat>y.dat;}
};
vector< pair<int,int> > v[N];
bool comp(Node x,Node y){return x.dat<y.dat;}
priority_queue<Node> q;
int find(int x){if(fa[x]==x) return x; return fa[x]=find(fa[x]);}
void New(int x){if(id[x]) return ; sta[++cnt]=x; id[x]=cnt;}
void insert(int x,int val)
{
	if(fa[x]!=x) return ; fa[x]=x+1; dis[x]=val;
	q.push((Node){x,dis[x]+s[sta[x]],true});
	for(auto it:v[x]) if(dis[it.first]>dis[x]+it.second)
		q.push((Node){it.first,dis[it.first]=dis[x]+it.second});
}
void solve(int fro)
{
	for(int i=1;i<=cnt+1;i++) dis[i]=INF,fa[i]=i;
	dis[fro]=0; q.push((Node){fro,0,false});
	while(!q.empty())
	{
		Node temp=q.top(); int x=temp.pos; q.pop();
		if(!temp.can){insert(x,temp.dat);continue;}
		for(auto it:v[x]) vis[it.first]=true;
		for(int i=find(1);i<=cnt;i=find(i+1)) if(!vis[i]) insert(i,temp.dat);
		for(auto it:v[x]) vis[it.first]=false;
	}
}
#undef int
int main()
{
	#define int long long
	freopen("happybean.in","r",stdin); freopen("happybean.out","w",stdout);
	n=read(); m=read(); for(int i=1;i<=n;i++) s[i]=read();
	for(int i=1,x,y,val;i<=m;i++)
		x=read(),y=read(),val=read(),New(x),New(y),
		v[id[x]].push_back(make_pair(id[y],val));
	for(int i=1;i<=n;i++) if(!id[i]&&s[i]<mn) mn=s[i],mnid=i; if(mn!=INF) New(mnid);
	for(int i=1;i<=n;i++) if(!id[i]) ans+=(n-1)*s[i];
	for(int i=1;i<=cnt;i++)
	{
		int minn=INF; solve(i);
		for(int j=1;j<=cnt;j++) ans+=dis[j],minn=min(minn,dis[j]+s[sta[j]]);
		ans+=(n-cnt)*minn;
	}
	printf("%lld",ans);
	return 0;
}

相關文章