9.16

9.16

疯掉——一堆ak的然鹅我只有121

(1)shopping——反悔堆

我的方法是直接开个原始价格优先队列和折扣价优先队列,然后每次比一比谁小就取谁,并且记录tk就是取过几次折扣价队列,超过k就不能再取。——考试时由于没有判队空导致最后一个点T飞了,由于数据水,本来可以100的

然鹅上述非正解;hack数据

2 1 5
2 1
1000 3
out 1
实际ans=2

错误原因就是我们排序时先把优惠券给了不需要优惠的,造成后面要优惠的没劵了

错误的ac代码

#include <queue>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <utility>
#include <iostream>
#include <algorithm>

using namespace std;
const int N=50005;
typedef long long LL;
inline LL read() {
	LL x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x;
}
int n,k,ans;
LL m,sum=0;
priority_queue< pair<int,int> > p;
priority_queue< pair<int,int> > Q;

bool vis[N];
int main() {
	n=read();k=read();m=read();
	for(int i=1;i<=n;i++) {
		p.push(make_pair(-read(),i));
		Q.push(make_pair(-read(),i));
	}
	int tk=1;
	while(!p.empty()&&!Q.empty()) {
		while(vis[p.top().second]&&p.size()) p.pop();
		if(p.empty()) break;
		while(vis[Q.top().second]&&Q.size()) Q.pop();
		if(Q.empty()) break;
		int x=-p.top().first,id1=p.top().second,xx=-Q.top().first,id2=Q.top().second;
		if(tk<=k&&xx<x) {
			tk++;sum+=xx;
			vis[id2]=1;
			Q.pop();
			if(sum<=m) ans++;
			else {
				printf("%d\n",ans);return 0;
			}
		} else {
			sum+=x;
			vis[id1]=1;
			p.pop();
			if(sum<=m) ans++;
			else {
				printf("%d\n",ans);return 0;
			}
		}
	}
	while(tk<=k&&!Q.empty()) {
		while(vis[Q.top().second]&&Q.size()) Q.pop();
		if(Q.empty()) break;
		int xx=-Q.top().first,id2=Q.top().second;
		tk++;sum+=xx;
		vis[id2]=1;
		Q.pop();
		if(sum<=m) ans++;
		else {
			printf("%d\n",ans);return 0;
		}		
	}
	while(!p.empty()) {
		while(vis[p.top().second]&&p.size()) 
			p.pop();
		if(p.empty()) break;
		int x=-p.top().first,id1=p.top().second;
		sum+=x;p.pop();
		vis[id1]=1;
		if(sum<=m) ans++;
		else {
			printf("%d\n",ans);return 0;
		}		
	}
	printf("%d\n",ans);
	
	return 0;
}

/*
4 1 7 
3 2 
2 2 
8 1 
4 3
*/

正解

先贪心取折扣价前k小的,然后取原始价的堆,维护反悔堆,当sum+p[i]>m&&sum+c[i]<=m时 ,我们可以从前k小的反悔一个,把劵给这个,那个用原价

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
const int N=50005;
typedef long long LL;
inline LL read() {
	LL x=0;char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x;
}
int n,k,ans;
LL m,sum=0;
struct node{
	int p,c;
}a[N]; 
bool cmp1(node a,node b) {
	return a.c==b.c?a.p<b.p:a.c<b.c;
}
bool cmp2(node a,node b) {
	return a.p<b.p;
}
priority_queue< int,vector<int>,greater<int> >q;
int main() {
	n=read();k=read();m=read();
	for(int i=1;i<=n;i++)
		a[i].p=read(),a[i].c=read();
	sort(a+1,a+1+n,cmp1);
	for(int i=1;i<=k;i++)
		if(sum+a[i].c<=m) 
			sum+=a[i].c,ans++,q.push(a[i].p-a[i].c);
	sort(a+1+k,a+1+n,cmp2);//k+1到n这些按p从小到大排序
	for(int i=k+1;i<=n;i++) {
		int x=a[i].p,flag=0;
		if(!q.empty()&&a[i].c+q.top()<x)
			flag=1,x=a[i].c+q.top();
		if(sum+x<=m) {
			sum+=x;ans++;
			if(flag) q.pop(),q.push(a[i].p-a[i].c);
		}
	}
	printf("%d\n",ans);
	return 0;
}


(2) tree——树形dp(传言贪心都能过)

简直了一开始发现性质一条边可以用两只企鹅站,这样的一条点对,越多越好;以为是二分图最大匹配,搞了半天发现假了,然后手模发现好像是个dfs(没想树形dP),本来想写个贪心,但时间不够再次挂挂挂,最后悲惨20分

啊啊啊想的和答案真的非常接近

dp[i][0]表示以 i 为根的子树中能够两两配对的最大点数,不包含节点 i。

树形dp真的蛮简单

0表示不选它,1表示选它

详细看代码注释

#include <queue>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <utility>
#include <iostream>
#include <algorithm>

using namespace std;
const int N=100005;
inline int read() {
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x;
}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
int n,T,k;
int hd[N],to[N<<1],nxt[N<<1],tot;
inline void add(int x,int y) {
	to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
}
int f[N][2],son[N];
void dfs(int x,int fa) {
	bool flag=0;int mx=-0x3f3f3f3f;
	for(int i=hd[x];i;i=nxt[i]) {
		int y=to[i];
		if(y==fa) continue;
		dfs(y,x);
		f[x][0]+=f[y][1];
		if(son[x]) {//很多儿子,我们先假设不选x-y的边,也就是先假设儿子都选
			flag=1;
			f[x][1]+=f[y][1];
			mx=max(mx,f[y][0]-f[y][1]);//找出最大的不选这个儿子的值
		} 
	}
	if(flag) f[x][1]+=mx+1;//最后选这个+1
}
int main() {
	T=read();
	while(T--) {
		memset(son,0,sizeof(son));
		memset(hd,0,sizeof(hd));
		memset(f,0,sizeof(f));
		tot=0;
		
		n=read();k=read();
		for(int i=2,x;i<=n;i++) {
			x=read();add(x,i);add(i,x);
			son[x]++;
		}
		dfs(1,0);
		int sum=max(f[1][1],f[1][0]);
		if(2*sum>=k) {
			printf("%d\n",(k+1)/2);
		} else {
			printf("%d\n",k-sum);//sum+(k-sum*2)  
		}
	} 
	return 0;
}


(3)room——bfs最短路

其实想法很简单,就是把钥匙状压一下,然后bfs最短路——只适合边权为 1的板子大概长这样——

	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;
	q.push(1);
	while(!q.empty()) {
		int x=q.front();q.pop();
		for(int i=hd[x];i;i=nxt[i]) {
			int y=to[i];
			...
            dis[y]=min(dis[x]+1,dis[y]);
            if(!vis[y]) {
            	vis[y]=1;
            	room[y]|=room[x];
            	q.push(y);
            } 
		}		
	}

回到这道题

我们发现对于y这个点,可能被之前的很多个x经过,然而你走路径只能从一个x走来,也就是说不能简单地每次room[y]|=room[x],因为有些钥匙是你最短路上取不到的,而你的room却记录下来了。

所以正解是对每个dis记录其状态,可能会存在为了取钥匙多走几步的情况。

只要经过一个点就入队,保证都被更新到——但这样如果往复经过一个点且状态不变就会往复更新,解决方法见代码

#include <queue>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <utility>
#include <iostream>
#include <algorithm>

using namespace std;
const int N=5005;
const int M=20005;
inline int read() {
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x;
}
int n,m,k,ans=0x3f3f3f3f;
int room[N],need[M];
int hd[N],to[M],nxt[M],tot;
inline void add(int x,int y) {
	to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
}
struct node{
	int id,dis,sta;
	node(){}
	node(int x,int y,int z):id(x),dis(y),sta(z){}
};
queue<int>q;
queue<int>Q;
int dis[N][(1<<11)];
bool vis[N];
int main() {
//	freopen("room20.in","r",stdin);
	n=read();m=read();k=read();
	for(int i=1;i<=n;i++) 
		for(int j=1;j<=k;j++)
			room[i]|=(read()<<j);
	for(int i=1,x,y;i<=m;i++) {
		x=read();y=read();
		add(x,y);
		for(int j=1;j<=k;j++)
			need[tot]|=(read()<<j);
	}
	memset(dis,0x3f,sizeof(dis));
	dis[1][room[1]]=0;
	q.push(1);Q.push(room[1]);
	while(!q.empty()) {
		int x=q.front();q.pop();
		int sta=Q.front();Q.pop();
		for(int i=hd[x];i;i=nxt[i]) {
			int y=to[i];
			if((sta|need[i])==sta) {
				if(dis[y][sta|room[y]]>=100000) {//保证每个点的这个状态只被更新1次
					dis[y][sta|room[y]]=min(dis[y][sta|room[y]],dis[x][sta]+1);
					q.push(y);Q.push(sta|room[y]);
				} 
			}				
		}		
	}
	for(int i=0;i<=(1<<k+1);i++)
		ans=min(ans,dis[n][i]);
	if(ans==dis[n+1][0]) printf("No Solution\n");
	else printf("%d\n",ans);
	return 0;
}

posted @ 2020-09-20 10:11  ke_xin  阅读(48)  评论(0编辑  收藏  举报