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
",ans);return 0;
}
} else {
sum+=x;
vis[id1]=1;
p.pop();
if(sum<=m) ans++;
else {
printf("%d
",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
",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
",ans);return 0;
}
}
printf("%d
",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
",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
",(k+1)/2);
} else {
printf("%d
",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
");
else printf("%d
",ans);
return 0;
}