NOIP复习模拟赛day3
1. 胖哥购物
(shopping.cpp/c/pas)
【问题描述】今天,胖哥在淘宝上购物。刚开始,胖哥的手上没有任何的优惠券。已知在购物过程中,胖哥将会按顺序遇到 n 个事件:1 x:购买一个价值为 x 的商品。当发生这种事时,胖哥最多只能使用一张面值小于 x的优惠券去减免。若手中没有优惠券,则全额购买。2 x:得到一张 x 元的优惠券。胖哥想知道,在最优的策略下,完成今天的购物最少要花掉多少钱。
【输入】共 n+1 行,第一行包含一个整数 n,表示事件的个数。接下来 n 行,每行两个正整数 type x。若 type=1,表示是事件 1,若 type=2,表示是事件 2。
【输出】输出共一行,包含一个整数,表示完成今天的购物最少要花掉多少钱。
【输入输出样例 1】10
2 4
1 6
1 6
2 4
1 1
2 2
2 3
2 3
1 3
1 6
12
【数据说明】对于 30%的数据,1≤n≤10。对于 60%的数据,1≤n≤1000。对于 100%的数据,1≤n≤100000。对于 100%的数据,1≤x≤10000
官方题解:
【考察算法】贪心
对于百分30的数据,暴力即可。
对于每次购物,如果手上有优惠券,不用白不用,每次贪心选取可减免金额最高的优惠券来使用,即可通过百分60的数据。
考虑百分100的数据,n=10w,每次线性去查找减免金额最高的优惠券的话,复杂度太高。
如何高效率地找出减免金额最高的优惠券?
假设现在手中有m张优惠券,我们要购买的物品价值为x,那么我们需要在这m个数中,找出小于x的最大的数。这里可以用权值线段树、平衡树等数据结构去维护,或者直接使用stl的map,时间复杂度O(nlogn)。
自己bb的题解:
我们考虑用一个set(万能的STL)来存储每次收集到的优惠券;
如果不会的请点我
然后就是一个lower_bound来找第一个大于等于当前价值优惠券;
找到完了以后把优惠券扔了就好了;
代码......
1 //NOIPRP++ 2 #include<bits/stdc++.h> 3 #define Re register int 4 using namespace std; 5 int N,Type,x; 6 long long ans; 7 multiset <int> s; 8 inline void read(int &x){ 9 x=0; char c=getchar(); bool p=1; 10 for (;'0'>c||c>'9';c=getchar()) if (c=='-') p=0; 11 for (;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); 12 p?:x=-x; 13 } 14 int main(){ 15 Re i,j; 16 read(N); 17 while (N--){ 18 read(Type); read(x); 19 if (Type==1){ 20 if (s.empty()){ans+=x; continue;} 21 multiset <int>::iterator it=s.lower_bound(x); 22 if (it==s.begin()){ans+=x; continue;} 23 ans+=x-*(--it); s.erase(it); 24 }else s.insert(x); 25 } 26 printf("%lld",ans); 27 return 0; 28 } 29 //NOIPRP++
其实,我考试用的不是这种方法;
我很平静的开了一个大根堆和一个小根堆;
然后手打堆;
嗯,居然奇迹般的过了......
1 //NOIPRP++ 2 #include<bits/stdc++.h> 3 #define Re register int 4 using namespace std; 5 int N,Type,x,Num,Big[100005],Small[100005],Top,top; 6 long long ans; 7 inline void read(int &x){ 8 x=0; char c=getchar(); bool p=1; 9 for (;'0'>c||c>'9';c=getchar()) if (c=='-') p=0; 10 for (;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); 11 p?:x=-x; 12 } 13 inline void Push_Big(int x){ 14 Big[++Top]=x; 15 for (Re i=Top;i^1&&Big[i>>1]<Big[i];i>>=1){Big[i]^=Big[i>>1];Big[i>>1]^=Big[i];Big[i]^=Big[i>>1];} 16 } 17 inline void Push_Small(int x){ 18 Small[++top]=x; 19 for (Re i=top;i^1&&Small[i>>1]>Small[i];i>>=1){Small[i]^=Small[i>>1];Small[i>>1]^=Small[i];Small[i]^=Small[i>>1];} 20 } 21 inline void Pop_Big() { 22 Big[1]=Big[Top--]; 23 for(Re p=1,q=2; q<=Top; p=q,q<<=1) { 24 if(q+1<=Top && Big[q]<Big[q+1]) ++q; 25 if(Big[p]<Big[q]){Big[p]^=Big[q];Big[q]^=Big[p];Big[p]^=Big[q];} 26 else break; 27 } 28 } 29 inline void Pop_Small() { 30 Small[1]=Small[top--]; 31 for(Re p=1,q=2; q<=top; p=q,q<<=1) { 32 if(q+1<=top && Small[q]>Small[q+1]) ++q; 33 if(Small[p]>Small[q]){Small[p]^=Small[q];Small[q]^=Small[p];Small[p]^=Small[q];} 34 else break; 35 } 36 } 37 int main(){ 38 Re i,j; 39 read(N); 40 for (i=1;i<=N;i++){ 41 read(Type);read(x); 42 if (Type==1){ 43 while (Top&&Big[1]>=x) Push_Small(Big[1]),Pop_Big(); 44 while (top&&Small[1]<x) Push_Big(Small[1]),Pop_Small(); 45 if (Top) ans+=x-Big[1],Pop_Big(); 46 else ans+=x; 47 } 48 else if (Type==2) Push_Big(x); 49 } 50 printf("%lld",ans); 51 return 0; 52 }//NOIPRP++
如果不手打堆......(也行,但是要看脸......)
1 //NOIPRP++ 2 #pragma GCC optimize("O2") 3 #include<bits/stdc++.h> 4 #define Re register int 5 using namespace std; 6 int N,Type,X; 7 long long ans; 8 priority_queue<int>q; 9 priority_queue <int, vector<int> ,greater<int> > p; 10 inline void read(int &x){ 11 x=0;char c=getchar();bool p=1; 12 for (;'0'>c||'9'<c;c=getchar()) if (c=='-') p=0; 13 for (;'0'<=c&&'9'>=c;c=getchar()) x=(x<<1)+(x<<3)+(c^48); 14 p?:x=-x; 15 } 16 int main(){ 17 Re i,j; 18 read(N); 19 for (i=1;i<=N;i++){ 20 read(Type); read(X); 21 if (Type==1){ 22 while (!q.empty()&&q.top()>=X) p.push(q.top()),q.pop(); 23 while (!p.empty()&&p.top()<X) q.push(p.top()),p.pop(); 24 if (!q.empty()) ans+=X-q.top(),q.pop(); 25 else ans+=X; 26 } 27 else q.push(X); 28 } 29 printf("%lld",ans); 30 return 0; 31 } 32 //NOIPRP++
2. 胖哥的树
(tree.cpp/c/pas)
【问题描述】胖哥有一棵 n 个节点的树,其中有一些节点是黑色的,另外一些节点是白色的。考虑这棵树上的一个 k 条边的边集。如果胖哥将这 k 条边从树上删除,则将会把这棵树分成 K+1 个联通块。现在,胖哥想知道,存在多少种这样的边集,使得删除这些边后,每个联通块只有一个黑色的节点。由于答案可能很大,请将答案 mod 1000000007(109+7)。
【输入】共三行,第一行包含一个整数 n,表示树的节点的个数。第二行有 n-1 个整数 P0,P1,...,Pn-2。pi 表示第(i+1)个节点与第 Pi 个节点之间有条边。假设树的节点的编号是从 0 到 n-1。第三个有 n 个整数 X0,X1,...,Xn-1(Xi=0 或 1)。如果 Xi等于 1,表示节点 i 是黑色,反之则表示节点 i 是白色。
【输出】输出共一行,包含一个整数,表示答案 mod 1000000007。
【输入输出样例 1】3
0 0
0 1 1
2
【输入输出样例 2】6
0 1 1 0 4
1 1 0 0 1 0
1
【输入输出样例 3】10
0 1 2 1 4 4 4 0 8
0 0 0 1 0 1 1 0 0 1
27
【数据说明】对于 30%的数据,2≤n≤10。对于 60%的数据,2≤n≤1000。对于 100%的数据,2≤n≤10000
官方题解:
【考察算法】树型DP
对于百分30的数据,枚举每条边选或者不选,然后统计即可。
对于大点的数据,考虑树型DP。
设dp[i][0]代表i这个联通块没有黑点的方案数,dp[i][1]代表有一个黑点的方案数。
假设父亲节点为u,考虑儿子节点的情况:
l 如果儿子节点是个有黑点的,父亲节点也有黑点,那么只能分裂开,不能合并在一起,只对dp[u][1]有贡献
l 如果儿子节点是个有黑点的,父亲节点没有黑点,那么可以分裂也可以合并,对dp[u][1]和dp[u][0]都有贡献
l 如果儿子节点是个没有黑点的,父亲节点有黑点,那么必须和父亲合并,对dp[u][1]有贡献
l 如果儿子节点是个没有黑点的,父亲节点也没黑点,那么必须和父亲合并,对dp[u][0]有贡献
边界状态:
l 如果第i个节点为黑色,那么dp[i][0]=0,dp[i][1]=1;
l 如果第i个节点为白色,那么dp[i][0]=1,dp[i][1]=0;
注意中间过程可能会超出int范围,要开long long
自己bb的题解:
其实官方题解已经讲得很清楚了;
那就看一下转移方程吧...
f[root][0]=0
f[root][1]=PI(f[son][0]+f[son][1])
else //root is white
f[root][0]=PI(f[son][0]+f[son][1])
f[root][1]=sigma(f[son][1]*PI(son'!=son)(f[son'][0]+f[son'][1]))
when inserting node v
f[root][1]*=f[v][0]+f[v][1]
f[root][1]+=f[v][1]*product
update product
1 //NOIPRP++ 2 #include<bits/stdc++.h> 3 #define Re register int 4 #define MOD 1000000007 5 #define LL long long 6 using namespace std; 7 int N,f[100005][2],u,v,Num,head[100005],Color[100005]; 8 struct Edge{ 9 int To,Next; 10 }edge[200005]; 11 inline void read(int &x){ 12 x=0; char c=getchar(); bool p=1; 13 for (;'0'>c||c>'9';c=getchar()) if (c=='-') p=0; 14 for (;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); 15 p?:x=-x; 16 } 17 inline void AddNum(int u,int v){ 18 edge[++Num].Next=head[u]; 19 edge[Num].To=v; 20 head[u]=Num; 21 } 22 inline void dfs(int Now,int Fa){ 23 f[Now][Color[Now]]=1; f[Now][Color[Now]^1]=0; 24 for (Re i=head[Now];i;i=edge[i].Next){ 25 int k=edge[i].To; 26 if (k==Fa) continue; dfs(k,Now); 27 f[Now][1]=(1LL*f[Now][1]*(f[k][0]+f[k][1])%MOD+1LL*f[Now][0]*f[k][1]%MOD)%MOD; 28 f[Now][0]=1LL*f[Now][0]*(f[k][0]+f[k][1])%MOD; 29 } 30 } 31 int main(){ 32 Re i,j; 33 read(N); 34 for (i=1;i<N;i++) read(v),AddNum(i,v),AddNum(v,i); 35 for (i=0;i<N;i++) read(Color[i]); 36 dfs(0,-1); printf("%d\n",f[0][1]); 37 return 0; 38 } 39 //NOIPRP++
第三题嘛......
我只会暴力,但是居然拿了55分,震惊......
先看一下我暴力的代码吧,简单易懂......
1 //NOIPRP++ 2 #include<bits/stdc++.h> 3 #define Re register int 4 using namespace std; 5 int N,M,Q,Num,head[100005],No[100005],u,v,x,y,Sum; 6 bool t[100005]; 7 char C[1]; 8 struct info{ 9 int x,y; 10 }f[100005]; 11 struct Edge{ 12 int To,Next; 13 }edge[400005]; 14 inline void read(int &x){ 15 x=0; char c=getchar(); bool p=1; 16 for (;'0'>c||c>'9';c=getchar()) if (c=='-') p=0; 17 for (;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); 18 p?:x=-x; 19 } 20 inline void AddNum(int u,int v){ 21 edge[++Num].Next=head[u]; 22 edge[Num].To=v; 23 head[u]=Num; 24 } 25 inline bool cmp(info a,info b){return a.x<b.x;} 26 inline void dfs(int Now){ 27 //cout<<Now<<endl; 28 f[++Sum].x=No[Now]; f[Sum].y=Now; t[Now]=false; 29 for (Re i=head[Now];i;i=edge[i].Next) if (t[edge[i].To]) dfs(edge[i].To); 30 } 31 int main(){ 32 Re i,j; 33 read(N);read(M); 34 for (i=1;i<=N;i++) read(x),No[i]=x; 35 for (i=1;i<=M;i++){ 36 read(u);read(v); 37 AddNum(u,v);AddNum(v,u); 38 } 39 read(Q); 40 while (Q--){ 41 scanf("%s",C);read(x);read(y); 42 if (C[0]=='B'){ 43 AddNum(x,y); AddNum(y,x); 44 } 45 else if (C[0]=='Q'){ 46 Sum=0;memset(f,0,sizeof(f)); memset(t,true,sizeof(t)); 47 dfs(x); sort(f+1,f+Sum+1,cmp); 48 if (Sum<y) printf("-1\n"); 49 else printf("%d\n",f[y].y); 50 } 51 } 52 return 0; 53 } 54 //NOIPRP++
然后正解怎么打呢???
我们需要开N个线段树来维护,但是开N个线段树显然内存会爆炸,所以我们要考虑动态开点
代码就看大佬的吧......