191015NOI模拟总结

T1:CF643D

解法:是一道模拟题。。。
我们可以用一个set维护全局最大值,然后再用n个set维护所有当前点的ans,用这n个set更新全局set即可完成3操作
考虑修改的影响,会影响到它和其原来父亲,并且父亲的E会改变从而导致父亲的父亲的答案也改变,新的父亲也是一样的,更新全局答案时注意处理三个点的环的情况,然后就是模拟了

Code:

#include<bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define ll long long
#define fi first
#define se second
#define db double
#define St set<int>::iterator
#define St1 set<ll>::iterator
#define Vt1 vector<ll>::iterator
using namespace std;
inline ll read(){
	ll res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int N=1e5+5;
int fa[N];ll ans[N],t[N];
multiset<ll>S;
struct cmp{
	inline bool operator () (const int &a,const int &b){
		if(ans[a]!=ans[b]) return ans[a]<ans[b];
		return a<b;
	}
};
struct info{
	set<int,cmp>s;
	int cnt;
	inline vector<ll> get(){
		vector<ll>ret;
		if(!s.empty()) ret.push_back(ans[*s.begin()]);
		if(s.size()>=2){
			St it=s.end();it--;
			ret.push_back(ans[*it]);
		}
		return ret;
	}
}E[N];
inline ll son(int x){return t[x]/(2+E[x].cnt);}
inline ll own(int x){return t[x]-(1+E[x].cnt)*son(x);}
int n,q;
inline void modify(){
	int a=read(),b=read();set<int>wk;
	wk.insert(a);wk.insert(fa[a]);wk.insert(fa[fa[a]]);wk.insert(b);wk.insert(fa[b]);
	set<int>wk1=wk;
	for(St it=wk.begin();it!=wk.end();it++) wk1.insert(fa[*it]);
	for(St x=wk1.begin();x!=wk1.end();x++){
		vector<ll>V=E[*x].get();
		for(Vt1 v=V.begin();v!=V.end();v++){
			St1 it=S.find(*v+son(*x));
			if(it!=S.end()) S.erase(it);
		}
	}
	for(St it=wk.begin();it!=wk.end();it++) E[fa[*it]].s.erase(*it);
	for(int st=-1;st<=1;st+=2){
		for(St x=wk.begin();x!=wk.end();x++){
			ans[*x]+=st*own(*x);
			for(St y=wk.begin();y!=wk.end();y++)
				if(fa[*y]==*x) ans[*x]+=st*son(*y);
		}
		if(st==-1) E[fa[a]].cnt--,fa[a]=b,E[b].cnt++;
	}
	for(St it=wk.begin();it!=wk.end();it++) E[fa[*it]].s.insert(*it);
	for(St x=wk1.begin();x!=wk1.end();x++){
		vector<ll> V=E[*x].get();
		for(Vt1 v=V.begin();v!=V.end();v++) S.insert(*v+son(*x));
	}
}
inline void file(){freopen("forest.in","r",stdin);freopen("forest.out","w",stdout);}
int main(){
	n=read(),q=read();
	for(int i=1;i<=n;i++) t[i]=read();
	for(int i=1;i<=n;i++){
		fa[i]=read();
		E[fa[i]].cnt++,E[fa[i]].s.insert(i);
	}
	for(int i=1;i<=n;i++){
		E[fa[i]].s.erase(i);
		ans[i]=own(i);
		for(St it=E[i].s.begin();it!=E[i].s.end();it++) ans[i]+=son(*it);
		E[fa[i]].s.insert(i);
	}
	for(int i=1;i<=n;i++){
		vector<ll>V=E[i].get();
		for(Vt1 v=V.begin();v!=V.end();v++) S.insert(*v+son(i));
	}
	while(q--){
		int op=read();
		if(op==1) modify();
		else if(op==2){int a=read();cout<<ans[a]+son(fa[a])<<"\n";}
		else{
			cout<<*S.begin()<<" ";
			St1 it=S.end();it--;
			cout<<*it<<"\n";
		}
	}
	return 0;
}

T2:TopCoder SRM 671 BearDestroys

很容易发现是轮廓线dp,但是裸的轮廓线不能过
考虑只压一维,我们发现会对一个点造成影响的只有其左方,上方,右上三个格子,那我们就可以只压对角线,复杂度就变成了 2 n ∗ ( n + m − 1 ) 2^n*(n+m-1) 2n(n+m1)

Code:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
int mod;
inline int add(int x,int y){x+=y;if(x>=mod) x-=mod;return x;}
inline int dec(int x,int y){x-=y;if(x<0) x+=mod;return x;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline void inc(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline void Dec(int &x,int y){x-=y;if(x<0) x+=mod;}
inline void Mul(int &x,int y){x=1ll*x*y%mod;}
const int N=55,S=(1<<15)|1;
int f[2][S],g[2][S];
int n,m,all;
int u[N],d[N];
inline int pt(int s,int d){return (s>>d&1);}
inline int re(int s,int d,int f){return pt(s,d)==f?s:(s^(1<<d));}
int main(){
	n=read(),m=read(),mod=read();
	all=1<<(n+1);
	for(int i=0;i<m;i++) u[i]=0;
	for(int i=m;i<n+m-1;i++) u[i]=i-m+1;
	for(int i=0;i<n-1;i++) d[i]=i;
	for(int i=n-1;i<n+m-1;i++) d[i]=n-1;
	int now=0;
	g[now][0]=1;f[now][0]=0;
	for(int i=0;i<n+m-1;i++){
		int to=now^1,nw=now;
		memset(f[to],0,sizeof(f[to]));
		memset(g[to],0,sizeof(g[to])); 
		for(int s=0;s<all;s++) if(g[nw][s] || f[nw][s]){
			int fl=pt(s,n);
			inc(g[to][re(s,n,0)<<1],g[nw][s]);
			inc(f[to][re(s,n,0)<<1],f[nw][s]);
			if(fl){
				inc(g[to][re(s,n,0)<<1],g[nw][s]);
				inc(f[to][re(s,n,0)<<1],f[nw][s]);	
			}
		}
		now^=1;
		for(int j=0;j<n;j++){
			int to=now^1,nw=now;
			memset(f[to],0,sizeof(f[to]));
			memset(g[to],0,sizeof(g[to]));
			for(int s=0;s<all;s++) if(g[nw][s] || f[nw][s]){
				int nf=f[nw][s],ng=g[nw][s];
				if(j<u[i] || j>d[i]){inc(f[to][re(s,j,0)],nf);inc(g[to][re(s,j,0)],ng);continue;}
				int f1=pt(s,j-1),f2=pt(s,j),f3=pt(s,j+1);
				if(!f2){
					if(f3){
						int fl=re(s,j,0);
						fl=re(fl,j+1,0);
						inc(f[to][fl],add(add(nf,nf),add(ng,ng)));
						inc(g[to][fl],add(ng,ng));
						if(j==n-1){
							fl=re(s,j,0);fl=re(fl,j+1,0);
							inc(f[to][fl],add(add(nf,nf),add(ng,ng)));
							inc(g[to][fl],add(ng,ng));
						}
						else{
							int fl=re(s,j,1);
							inc(f[to][fl],nf),inc(g[to][fl],ng);
						}
					}
					else{
						int fl=re(s,j,1);
						inc(f[to][fl],nf),inc(g[to][fl],ng);
					}
				}
				else{
					int fl=re(s,j,0);inc(nf,nf),inc(ng,ng);
					if(!f1){
						inc(f[to][fl],add(add(nf,nf),add(ng,ng)));
						inc(g[to][fl],add(ng,ng));
					}
					else{
						inc(f[to][fl],add(nf,ng));
						inc(g[to][fl],ng);
					}
				}
			}
			now^=1;
		}
	}
	int ans=0;
	inc(ans,f[now][0]);
	inc(ans,mul(2,f[now][1<<n-1]));
	inc(ans,mul(2,f[now][1<<n]));
	inc(ans,mul(4,f[now][3<<n-1]));
	cout<<ans;
	return 0;
}

T3:
你有n种果汁,每种果汁有一个体积,你要找到尽量少的容量无限的桶,把所有果汁放进桶中,满足以下条件:
1.一个桶最多放两种果汁
2.任意两个桶放的果汁的体积相同
果汁的体积可以是实数

第一个引理:n种果汁最多需要n-1个桶
证明:考虑数学归纳法, n = 2 n=2 n=2显然成立
n > 2 n\gt2 n>2时,考虑最少的和最多的,它们加起来一定大于等于平均值*2,那就把全部最小的和一部分最大的放进去,就变成了n-1的情况

第二个引理:如果有把a种果汁放进b个桶的方案,则一定可以放进a-b个桶,每个桶的果汁平均数不变

证明:考虑把有某种果汁的所有桶连边,则连通块个数至少为a-b
考虑形成连通块的某x个桶,有2x个空档,连通块内部的每一条边都会使不同的果汁数-1,那么最多只有2x-(x-1)=x+1种。
如果某个连通块里有超过x-1条边,那么总的连通块个数显然会增加,直接把这个连通块并到别的连通块里去就行了。

则我们就是要把所有数分成尽量多的段,使各段平均数相等

先枚举答案,再搜索枚举集合就完了

Code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
int n,all,a[25];ll suma;
int sum[2333333],cnt[2333333],num[2333333];
inline bool check(int x){
	int s=0;
	while(1){
		int t=all^s,fl=0;
		if(1ll*(cnt[t]-1)*suma==1ll*sum[t]*x) return 1;
		for(int tt=t;tt;tt=(tt-1)&t)
		if(1ll*(cnt[tt]-1)*suma==1ll*sum[tt]*x){fl=1;s^=tt;break;}
		if(!fl) return 0;
	}
}
int main(){
	n=read();all=(1<<n)-1;
	if(n<=2){puts("1");return 0;}
	for(int i=1,k=1;i<=n;i++,k<<=1) a[i]=read(),suma+=a[i],num[k]=a[i];
	for(int s=1;s<=all;s++) sum[s]=sum[s^(s&-s)]+num[s&-s],cnt[s]=cnt[s^(s&-s)]+1;
	for(int i=(n+1)/2;i<n;i++) if(check(i)){cout<<i;return 0;}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值