1. 程式人生 > 其它 >NOIP模擬84(多校17)

NOIP模擬84(多校17)

「寶藏·尋找道路·豬國殺·數樹」on 10.27

T1 寶藏

解題思路

考場上一眼出 \(nlog^2\) 做法,然後沒看見是 1s 3e5 的資料,我竟然以為自己切了??

考完之後嘗試著把二分改為指標的移動,然後就過了??或許是資料水吧,感覺自己的做法指標好像並不滿足單調性。。

口胡一下正解,做法差不多,只不過列舉的方式改變了,但是都需要先對於 w 進行排序,列舉每一種長度的序列,單調指標維護最大的合法的值。

這個是有單調性的,然後主席樹或者權值線段樹維護均可。

code

其實是假做法

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
#define ls x<<1
#define rs x<<1|1
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=3e5+10,M=1e6+10;
int n,m,q,lim,ans[N];
struct Node{int w,t;}s[N];
struct Segment_Tree
{
	int root,all;
	struct node{int siz,dat;}tre[M<<2];
	#define push_up(x) tre[x].dat=tre[ls].dat+tre[rs].dat,tre[x].siz=tre[ls].siz+tre[rs].siz
	void insert(int x,int l,int r,int pos,int val)
	{
		if(l==r) return tre[x].siz+=val,tre[x].dat+=val*pos,void();
		int mid=(l+r)>>1;
		if(pos<=mid) insert(ls,l,mid,pos,val);
		else insert(rs,mid+1,r,pos,val);
		push_up(x);
	}
	int query(int x,int l,int r,int k)
	{
		if(!tre[x].siz||!k) return 0;
		if(l==r) return tre[x].dat*k/tre[x].siz;
		int mid=(l+r)>>1;
		if(tre[ls].siz>k) return query(ls,l,mid,k);
		return tre[ls].dat+query(rs,mid+1,r,k-tre[ls].siz);
	}
}T1,T2;
bool comp(Node x,Node y){return x.w<y.w;};
#undef int
int main()
{
	#define int long long
	freopen("treasure.in","r",stdin); freopen("treasure.out","w",stdout);
	n=read(); m=read(); q=read(); memset(ans,-1,sizeof(ans));
	for(int i=1;i<=n;i++) s[i].w=read(),s[i].t=read(),lim=max(lim,s[i].t);
	sort(s+1,s+n+1,comp); for(int i=1;i<=n;i++) T2.insert(1,0,lim,s[i].t,1);
	for(int i=1,val=0;i<=n;i++)
	{
		T2.insert(1,0,lim,s[i].t,-1); val=min(val,min(i-1,n-i));
		while(val<min(i-1,n-i)&&T1.query(1,1,lim,val+1)+T2.query(1,1,lim,val+1)+s[i].t<=m) val++;
		while(val>0&&T1.query(1,1,lim,val)+T2.query(1,1,lim,val)+s[i].t>m) val--;
		ans[val]=max(ans[val],s[i].w); T1.insert(1,0,lim,s[i].t,1);
	}
	for(int i=n/2;i>=0;i--) ans[i]=max(ans[i],ans[i+1]);
	while(q--){int x;x=read();printf("%lld\n",ans[min(n,x/2)]);}
	return 0;
} 

T2 尋找道路

解題思路

首先考慮去除前導 0 的影響,直接搜尋一遍查詢所有到 1 節點距離為 0 的點記錄下來就好了。

剩下的部分就是字典序以及長度的問題了,那麼長度的問題直接 BFS 就可以了。

字典序大小的話,對於佇列中長度一致並且數字序列相同的一起拿出來,然後優先掃 0 邊權的邊再掃 1 邊權的邊。

標記一下,保證每個節點只訪問一次。

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,mod=1e9+7,INF=1e18;
int n,m,top,sta[N],len[N],dis[N];
int tot=1,head[N],ver[N<<1],nxt[N<<1],edge[N<<1];
vector<int> dis0;
queue<int> q;
bool vis[N];
void add_edge(int x,int y,int val)
{
	ver[++tot]=y; edge[tot]=val;
	nxt[tot]=head[x]; head[x]=tot;
}
#undef int
int main()
{
	#define int long long
	freopen("path.in","r",stdin); freopen("path.out","w",stdout);
	n=read(); m=read(); memset(len,0x3f,sizeof(len)); dis[1]=len[1]=0;
	for(int i=1,x,y,z;i<=m;i++) x=read(),y=read(),z=read(),add_edge(x,y,z);
	q.push(1); dis0.push_back(1);
	while(!q.empty())
	{
		int x=q.front(); q.pop();
		for(int i=head[x];i;i=nxt[i])
			if(!vis[ver[i]]&&!edge[i])
				vis[ver[i]]=true,dis0.push_back(ver[i]),q.push(ver[i]);
	}
	for(auto it:dis0) q.push(it),dis[it]=len[it]=0;
	while(!q.empty())
	{
		top=0; sta[++top]=q.front(); q.pop();
		while(!q.empty()&&len[q.front()]==len[sta[1]]&&dis[q.front()]==dis[sta[1]])
			sta[++top]=q.front(),q.pop();
		for(int i=1;i<=top;i++)
		{
			int x=sta[i];
			for(int j=head[x];j;j=nxt[j])
			{
				int to=ver[j],val=edge[j];
				if(val||vis[to]||len[to]<=len[x]+1) continue;
				len[to]=len[x]+1; dis[to]=dis[x]*2%mod;
				vis[to]=true; q.push(to);
			}
		}
		for(int i=1;i<=top;i++)
		{
			int x=sta[i];
			for(int j=head[x];j;j=nxt[j])
			{
				int to=ver[j],val=edge[j];
				if(!val||vis[to]||len[to]<=len[x]+1) continue;
				len[to]=len[x]+1; dis[to]=(dis[x]*2+1)%mod;
				vis[to]=true; q.push(to);
			}
		}
	}
	for(int i=2;i<=n;i++) printf("%lld ",len[i]>=INF?-1ll:dis[i]);
	return 0;
}

T3 豬國殺

解題思路

其實是個假期望,計數 DP 。

我們只需要知道每一種方案的總和了,最後乘上一個 \(A^n\)

\(g_{i,j,k}\) 表示有 多少個⻓度為 \(i\) 的正整數序列滿足每一個數字不大於 \(j\) 且所有數字總和不超過 \(k\)

假設我們能夠求出來這個值,考慮如何計算答案。

列舉選的牌中的最大值 \(j\) ,最大值個數 \(k\) ,以及選了 \(i\) 個小於 \(j\) 的牌,於是就有了:

\[\sum\limits_{i=0}^n\sum\limits_{j=1}^A\sum\limits_{k=1}^{n-i}g_{i,j-1,m-j\times k}\times \binom{n}{i}\sum\limits_{t=k}^{n-i}\binom{n-i}{t}\times(A-j)^{n-i-t} \]

對於計算過方案數的兩個序列就可以視為序列中的元素是等價的了,也就是再乘上一個可重集排列。

因為我們要選擇 \(k\)\(j\) 但是序列中不一定只有 \(k\)\(j\) 因此我們需要讓前面的牌的總和是 \(m-j\times k\) 然後列舉後面有多少個 \(j\) 同時計算剩下的取值的個數。

對於 \(g_{i,j,k}\) 可以通過列舉多少個大於 \(j\) 的數字進行計算,可以運用擋板法,由於擋板之間 1 的個數可能會超過 \(j\) 因此需要容斥一下:

\[g_{i,j,k}=\sum\limits_{t=0}^i(-1)^t\binom{i}{t}\binom{k-t\times j}{i} \]

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=110,M=1010,mod=998244353;
int n,m,lim,mx,ans,fac[M],ifac[M];
void add(int &x,int y){x+=y;if(x>=mod)x-=mod;}
int power(int x,int y,int p=mod)
{
	int temp=1;
	while(y)
	{
		if(y&1) temp=temp*x%p;
		x=x*x%p; y>>=1;
	}
	return temp;
}
int C(int x,int y){return x<y?0:fac[x]*ifac[y]%mod*ifac[x-y]%mod;}
int g(int i,int j,int k)
{
	int sum=0;
	for(int p=0,sym=1;p<=i;p++,sym=-sym)
	{
		int temp=C(i,p)*C(k-p*j,i)%mod;
		if(temp) add(sum,sym*temp+((~sym)?0:mod));
		else break;
	}
	return sum;
}
#undef int
int main()
{
	#define int long long
	freopen("legend.in","r",stdin); freopen("legend.out","w",stdout);
	n=read(); m=read(); lim=read(); mx=max(n,max(m,lim));
	fac[0]=ifac[0]=1; for(int i=1;i<=mx;i++) fac[i]=fac[i-1]*i%mod;
	ifac[mx]=power(fac[mx],mod-2); for(int i=mx-1;i>=1;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
	for(int i=0;i<=n;i++)
		for(int j=1;j<=lim;j++)
			for(int k=1;k<=n-i;k++)
			{
				int base=0,temp=C(n,i)%mod*g(i,j-1,m-j*k)%mod;
				if(!temp) continue;
				for(int p=k;p<=n-i;p++)
				{
					int temp=C(n-i,p); if(!temp) break;
					add(base,temp*power(lim-j,n-i-p)%mod);
				}
				if(base) add(ans,base*temp%mod);
			}
	printf("%lld",ans*power(power(lim,n),mod-2)%mod); return 0;
}

T4 數樹

解題思路

列舉以哪個節點為根以及子樹和 T2 的匹配程度來判斷。

具體實現可以 Hash+素數 防止重複。

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=3e3+10,M=20,bas=23,mod=998244353;
int n,m,cnt,ans,pri[N],siz[N];
int tot=1,head[N],ver[N<<1],nxt[N<<1];
ull has[N];
unordered_map<ull,int> ys,f[N];
unordered_map<ull,bool> can,mp;
vector<int> v[M];
bitset<N> vis;
void add_edge(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
void Get_Prime()
{
	for(int i=2;i<=1000;i++)
	{
		if(!vis[i]) pri[++cnt]=i;
		for(int j=1;j<=cnt&&pri[j]*i<=1000;j++)
		{
			vis[i*pri[j]]=true;
			if(i%pri[j]==0) break;
		}
	}
}
void pre_dfs(int x,int fa)
{
	siz[x]=has[x]=1;
	for(auto to:v[x])
	{
		if(to==fa) continue;
		pre_dfs(to,x); siz[x]+=siz[to];
		has[x]+=has[to]*pri[siz[to]+bas];
	}
	mp.insert(make_pair(has[x],true));
	ys.insert(make_pair(has[x],siz[x]+bas));
}
void dfs(int x,int fa)
{
	f[x].insert(make_pair(1,1));
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i]; if(to==fa) continue;
		unordered_map<ull,int> temp=f[x]; dfs(to,x);
		for(auto it1:temp) for(auto it2:f[to])
		{
			ull rec=it1.first+it2.first*pri[ys.find(it2.first)->second];
			f[x][rec]=(f[x][rec]+it1.second*it2.second)%mod;
		}
	}
	vector<ull> dela;
	for(auto it:f[x]) if(mp.find(it.first)==mp.end()) dela.push_back(it.first);
	for(auto it:dela) f[x].erase(it);
}
#undef int
int main()
{
	#define int long long
	freopen("count.in","r",stdin); freopen("count.out","w",stdout);
	n=read(); Get_Prime();
	for(int i=1,x,y;i<n;i++)
		x=read(),y=read(),
		add_edge(x,y),add_edge(y,x);
	m=read();
	for(int i=1,x,y;i<m;i++)
		x=read(),y=read(),
		v[x].push_back(y),v[y].push_back(x);
	for(int i=1;i<=m;i++) pre_dfs(i,0),can.insert(make_pair(has[i],true));
	dfs(1,0);
	for(auto it:can)
		for(int i=1;i<=n;i++) 
			if(f[i].find(it.first)!=f[i].end())
				ans=(ans+f[i].find(it.first)->second)%mod;
	printf("%lld",ans);
	return 0;
}