1. 程式人生 > 其它 >noip模擬23[聯·賽·題]

noip模擬23[聯·賽·題]

\(noip模擬23\;solutions\)

怎麼說呢??這個考試考得是非常的慘烈,一共拿了70分,為啥呢

因為我第一題和第三題爆零了,然後第二題拿到了70分,還是貪心的分數

第一題和第二題我調了好久,hhhh

害,害,害,害

·

\(T1\;聯\)

據出題人說,這是個線段樹裸題,啊啊啊,我看到1e5的時候也覺得這是個簡單的線段樹

後來看到1e18我就溜走了,後來回來看,發現這個可以\(O(n^2)\)連結串列做

打對了1,2操作,忘記換行了0分,應該是30分

這個題說白了就是利用線段樹維護值,不過你發現他的範圍極其大

但是,這裡面有用的值吧,只有這些區間的端點和這些區間右端點的右邊那一位

所以我們要做的就是一個離散化,然後就很容易找到了這第一個0

再看看操作,1,2直接下放,3的話就追上1,2的時候給他的laz標記異或一下

程式碼實現極其簡單,我因為pushdown沒有改當前值WA了好長時間

注意離散化要把1也弄進去,離散化陣列開打一點

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1e5+5;
int m;
int typ[N];
ll a[N],b[N];
ll lsh[N*10],lh;
map<long long,int> ys;
struct TREE{
	#define ls x<<1
	#define rs x<<1|1
	int laz[N*16],now[N*16],mn[N*16];
	TREE(){}
	void pushup(int x){
		mn[x]=min(mn[ls],mn[rs]);
		return ;
	}
	void build(int x,int l,int r){
		mn[x]=l;
		if(l==r){
			return ;
		}
		now[x]=-1;
		int mid=l+r>>1;
		build(ls,l,mid);
		build(rs,mid+1,r);
		return ;
	}
	void pushdown(int x,int l,int r){
		if(now[x]==-1)return ;
		int mid=l+r>>1;
		now[ls]=now[rs]=now[x];
		if(now[x]==0){
			mn[ls]=l;mn[rs]=mid+1;
		}
		else if(now[x]==1)mn[ls]=mn[rs]=0x3f3f3f3f;
		now[x]=-1;
		return ;
	}
	void ins(int x,int l,int r,int ql,int qr,int v){
		//if(v==1)cout<<x<<" "<<l<<" "<<r<<" "<<ql<<" "<<qr<<" "<<now[x]<<" "<<mn[x]<<endl;
		if((ql<=l&&r<=qr)&&(v!=3||now[x]!=-1)){
			if(v==1)now[x]=1;
			if(v==2)now[x]=0;
			if(v==3){
				now[x]^=1;
			}
			if(now[x]==0)mn[x]=l;
			if(now[x]==1)mn[x]=0x3f3f3f3f;
			return ;
		}
		pushdown(x,l,r);
		int mid=l+r>>1;
		if(ql<=mid)ins(ls,l,mid,ql,qr,v);
		if(qr>mid)ins(rs,mid+1,r,ql,qr,v);
		pushup(x);
		//if(v==1)cout<<x<<" "<<l<<" "<<r<<" "<<ql<<" "<<qr<<" "<<now[x]<<" "<<mn[x]<<endl;
		return ;
	}
}xds;
signed main(){
	scanf("%d",&m);
	lsh[++lh]=1;
	for(re i=1;i<=m;i++){
		scanf("%d%lld%lld",&typ[i],&a[i],&b[i]);
		lsh[++lh]=a[i];
		lsh[++lh]=b[i];
		lsh[++lh]=b[i]+1;
	}
	sort(lsh+1,lsh+lh+1);
	lh=unique(lsh+1,lsh+lh+1)-lsh-1;
	for(re i=1;i<=lh;i++)ys[lsh[i]]=i;//cout<<i<<" "<<lsh[i]<<endl;
	xds.build(1,1,lh);
	for(re i=1;i<=m;i++){
		a[i]=ys[a[i]];
		b[i]=ys[b[i]];
		//cout<<a[i]<<" "<<b[i]<<endl;
		xds.ins(1,1,lh,a[i],b[i],typ[i]);
		//cout<<xds.now[64]<<" ";
		printf("%lld\n",lsh[xds.mn[1]]);
	}
}

·

\(T2\;賽\)

這個題考場上我只會打貪心,拿到了70pts

最後我發現,這個貪心正確性果然保證不了,因為你無法列舉完所有的情況

並且你根本不知道要選多少共同喜歡的物品才能達到最有解

所以我們就列舉,先列舉一個共同喜歡的個數,再列舉在各自喜歡的中選多少

最後在剩餘的中尋找前幾個,最後的這個用線段樹,要不然複雜度就炸了

這裡有一堆邊界之要維護,諸如,是否有足夠m個共同喜歡的。。。。

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=2e5+5;
int n,m,k,a[N],b[N];
bool via[N],vib[N],vis[N];
int val[N];
int cc[N],ac[N],bc[N];
ll ans,res;
struct node{
	int ls[N*40],rs[N*40];
	ll sum[N*40],siz[N*40];
	int seg;
	void pushup(int x){
		sum[x]=sum[ls[x]]+sum[rs[x]];
		siz[x]=siz[ls[x]]+siz[rs[x]];
	}
	void ins(int &x,int l,int r,int pos){
		if(!x)x=++seg;
		if(l==r){
			siz[x]+=1;
			sum[x]+=pos;
			return ;
		}
		int mid=l+r>>1;
		if(pos<=mid)ins(ls[x],l,mid,pos);
		else ins(rs[x],mid+1,r,pos);
		pushup(x);return ;
	}
	void del(int x,int l,int r,int pos){
		if(l==r){
			siz[x]-=1;
			sum[x]-=pos;
			return ;
		}
		int mid=l+r>>1;
		if(pos<=mid)del(ls[x],l,mid,pos);
		else del(rs[x],mid+1,r,pos);
		pushup(x);return ;
	}
	ll query(int x,int l,int r,int rak){
		if(!rak)return 0;
		if(!x)return 0;
		if(!siz[x])return 0;
		if(rak==siz[x])return sum[x];
		int mid=l+r>>1;ll ret=0;
		if(rak>siz[ls[x]]){
			ret+=query(ls[x],l,r,siz[ls[x]]);
			ret+=query(rs[x],l,r,rak-siz[ls[x]]);
		}
		else ret+=query(ls[x],l,r,rak);
		return ret;
	}
}xds;
int R,rt;
bool cmp(int x,int y){
	return val[x]<val[y];
}
signed main(){
	ans=0x3f3f3f3f3f3f3f3f;
	scanf("%d%d%d",&n,&m,&k);
	for(re i=1;i<=n;i++)scanf("%d",&val[i]),R=max(R,val[i]);
	scanf("%d",&a[0]);
	for(re i=1;i<=a[0];i++)scanf("%d",&a[i]);
	scanf("%d",&b[0]);
	for(re i=1;i<=b[0];i++)scanf("%d",&b[i]);
	for(re i=1;i<=a[0];i++)via[a[i]]=true;
	for(re i=1;i<=b[0];i++)vib[b[i]]=true;
	for(re i=1;i<=a[0];i++)if(vib[a[i]])cc[++cc[0]]=a[i];
	for(re i=1;i<=a[0];i++)if(!vib[a[i]])ac[++ac[0]]=a[i];
	for(re i=1;i<=b[0];i++)if(!via[b[i]])bc[++bc[0]]=b[i];
	int nc=m,na;
	while(nc>cc[0])nc--;
	if(k-nc>ac[0]||k-nc>bc[0]||nc<2*k-m){
		printf("-1");
		return 0;
	}
	//cout<<"sb"<<endl;
	sort(cc+1,cc+cc[0]+1,cmp);
	sort(ac+1,ac+ac[0]+1,cmp);
	sort(bc+1,bc+bc[0]+1,cmp);
	for(re i=1;i<=nc;i++)vis[cc[i]]=true,res+=val[cc[i]];
	for(re i=1;i<=k-nc;i++)vis[ac[i]]=true,res+=val[ac[i]];
	for(re i=1;i<=k-nc;i++)vis[bc[i]]=true,res+=val[bc[i]];
	for(re i=1;i<=n;i++)if(!vis[i])xds.ins(rt,1,R,val[i]);
	na=max(k-nc,0);
	//cout<<"sb"<<endl;
	//cout<<res<<" "<<na<<" "<<nc<<" "<<xds.query(rt,1,R,m-2*na-nc)<<endl;
	ans=min(ans,res+xds.query(rt,1,R,m-na*2-nc));
	for(re i=nc;i>=max(1,2*k-m);i--){
		if(na>ac[0]||na>bc[0])break;
		res-=val[cc[i]];vis[cc[i]]=false;
		xds.ins(rt,1,R,val[cc[i]]);
		if(k-i+1>0){
			res+=val[ac[k-i+1]];vis[ac[k-i+1]]=true;
			xds.del(rt,1,R,val[ac[k-i+1]]);
			res+=val[bc[k-i+1]];vis[bc[k-i+1]]=true;
			xds.del(rt,1,R,val[bc[k-i+1]]);
			na++;
		}
		if(na*2+i-1>m)break;
		//cout<<ans<<" "<<na<<" "<<i<<endl;
		ans=min(ans,res+xds.query(rt,1,R,m-2*na-i+1));
		//cout<<ans<<" "<<res<<" "<<cc[i]<<" "<<ac[k-i+1]<<" "<<bc[k-i+1]<<endl;
	}
	printf("%lld",ans);
}

調的時候千萬不要著急,得耐心的調,要不然肯定調不出來

·

\(T3\;題\)

這個我第一眼看上去就是一個圖論,然後沒有想到怎麼做

最後dfs也沒有打對,就直接0分了,其實就是一個大列舉。。。。

維護一個\(bool\)陣列\(f[i][j]\)

1表示,如果想讓i存在,那麼必須刪掉j,0就不用了唄

那麼偶們就有了一個轉移,每次列舉u,v

如果\(f[i][u]和f[i][v]\)都為1,那麼這個蘋果就不可能存在了,注意uv是從m到1的

因為你後面必須要刪掉u,v,而你現在就要刪掉一個,那後面的條件就不能滿足了

如果只有其中一個為1,那就把另一個也標記成1

最後我們美劇可能存在的點,如果他們必須刪掉的點集中沒有交集,那就合法

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=405;
const int M=5e4+5;
int n,m;
int f[N][N],g[N];
int u[M],v[M];
int ans;
signed main(){
	scanf("%d%d",&n,&m);
	for(re i=1;i<=m;i++)scanf("%d%d",&u[i],&v[i]);
	for(re i=1;i<=n;i++)f[i][i]=1,g[i]=1;
	for(re i=1;i<=n;i++){
		for(re j=m;j>=1;j--){
			if(f[i][u[j]]&&f[i][v[j]])g[i]=0;
			else if(f[i][u[j]])f[i][v[j]]=1;
			else if(f[i][v[j]])f[i][u[j]]=1;
		}
	}
	for(re i=1;i<=n;i++){
		for(re j=1;j<=n;j++){
			int flag=0;
			if(!g[i]||!g[j])continue;
			for(re k=1;k<=n;k++){
				if(f[i][k]&&f[j][k])
					flag=1;
			}
			if(flag==0)ans++;
		}
	}
	printf("%d",ans/2);
}