[EOJ]2019 ECNU XCPC March Selection #4
solved 4
rank 2
A(树)
题意:给出一棵树,有m条路径已经被染色,重复染色算作多次,选出一条最长的路径 (p,q),满足 p 为 q 的祖先,且 (p,q) 被至少 k 条染色路径完全覆盖。
unsolved
B(思维)
题意:现在给你一列 n 个数,每个数各不相同,要求重新排列后这个数组记录的完全二叉树是一个小根堆,若有多种方法,输出字典序最大的一组。
首先根节点肯定为最小的数,之后剩下n-1个数,要使字典序最大,首先要让左儿子最大,至少要有(n-3)/2个数要比左儿子大,剩下的数都放在右边,然后就可以递归解决了,考场上用的BFS实现。
(因为数组开小,忘记注释掉文件读写WA了好几次)
#include<bits/stdc++.h> using namespace std; #define LL long long #define rep(i,n) for(int i=1;i<=n;i++) #define pii pair<int,int> #define mp make_pair #define pb push_back #define st first #define nd second const int N=1e5+7; const int mod=1e8+7; int cmp(int a,int b){ return a>b; } int a[N],b[N]; int cnt; int main(){ int n; cin>>n; rep(i,n)cin>>a[i]; sort(a+1,a+1+n); queue<pii> q; q.push(mp(1,n)); while(!q.empty()){ pii h=q.front(); q.pop(); b[++cnt]=a[h.st]; if(cnt==n)break; q.push( mp( h.st+1+(h.nd-1)/2,(h.nd-1)/2) ); q.push( mp( h.st+1,(h.nd-1)/2) ); } rep(i,n){ cout<<b[i]; if(i!=n)cout<<" "; else cout<<endl; } }
00:51(4A)
C(组合数学)
题意:n个相同的球放到m个相同盒子,求方案数。
f(n,m)=f(n,m-1)+f(n-m,m),记忆化搜索实现,DP也行。
(想当然以为模数是 1e9+7 WA了一发)
#include<bits/stdc++.h> using namespace std; #define LL long long #define rep(i,n) for(int i=1;i<=n;i++) #define pii pair<int,int> #define mp make_pair #define pb push_back const int N=300+7; const int mod=1e8+7; LL mem[N][N]; LL solve(int m,int n){ if(m<0)return 0; if(mem[m][n])return mem[m][n]; if(m==1||n==1)return 1; mem[m][n]=(solve(m-n,n)%mod+solve(m,n-1)%mod)%mod; return mem[m][n]; } int main(){ int t; cin>>t; while(t--){ int n,m; cin>>m>>n; cout<<solve(m,n)<<endl; } }
00:16(2A)
D(暴力)
题意:n<=40,V<=1e12的0/1背包
观察发现V特别大,n特别小,考虑暴搜。2^40难以接受,考虑分成两半,分别2^20爆搜出方案,然后对于其中一边的一个方案(w1,v1),在另一边中找(w2,v2)满足w1+w2<=W并使v1+v2最大,二分查找实现。复杂度(2^(n/2)*log(2^(n/2)))
#include<bits/stdc++.h> using namespace std; #define LL long long #define rep(i,n) for(int i=1;i<=n;i++) #define pii pair<int,int> #define mp make_pair #define pb push_back #define st first #define nd second const int N=300; const int mod=1e8+7; const LL inf=1e18; LL w[N],v[N],q[20],ans[20]; pair<LL,LL> a[(1<<23)]; int main(){ int n,Q; cin>>n; rep(i,n)cin>>w[i]>>v[i]; cin>>Q; rep(i,Q)cin>>q[i]; int m=n/2; for(int i=0;i<(1<<m);i++){ LL sw=0,sv=0; for(int j=1;j<=m;j++){ if((i>>(j-1))&1){ sw+=w[j]; sv+=v[j]; } } a[i]=(mp(sw,sv)); } sort(a,a+(1<<m)); int cnt=1; for(int i=1;i<1<<m;i++){ if(a[cnt-1].nd<a[i].nd)a[cnt++]=a[i]; } for(int i=0;i<(1<<(n-m));i++){ LL sw=0,sv=0; for(int j=1;j<=(n-m);j++){ if((i>>(j-1))&1){ sw+=w[j+m]; sv+=v[j+m]; } } rep(i,Q){ if(sw<=q[i])ans[i]=max(ans[i], (( lower_bound( a, a+cnt, mp(q[i]-sw,inf) ) -1 ) ->nd )+sv); } } rep(i,Q)cout<<ans[i]<<endl; }
01:41(1A)
E
题意:给出一个数列,元素是1-40的正整数,两个相邻且相等的数可以合成为一个比原数大1的数,求合并若干次后可以得到的最大的数。
unsolved
F(DP)
题意:给定 n 个整数 ai ,一次操作可以改变该数列中任意一个数加 1 或者减 1 ,问最少需要多少次操作可以使得这个序列变成不下降序列。
比较明显的DP,f[i][j]表示前i个数,以j结尾的最少操作次数,显然有转移方程:f[i][j]=min{ f[i-1][k],k<=j }+abs(a[i]-j),时间复杂度太高,考虑优化。可以猜想至少有一种最优方案使得变换后所有数组成的集合是原先集合的子集,即最优方案不会出现原来没出现过的数字,证明略。这样看复杂度貌似是n^3的,但其实min{ f[i-1][k],k<=j }部分可以通过从小到大递推出来,只要改变下枚举顺序即可。最终复杂度O(n^2),理论上还可以通过去重减小下常数,实际上因为数据的原因跑的还没原来快。。
#include<bits/stdc++.h> using namespace std; #define LL long long #define rep(i,n) for(int i=1;i<=n;i++) #define pii pair<int,int> #define mp make_pair #define pb push_back #define st first #define nd second const int N=5e3+7; const LL inf=1e18; LL a[N],b[N],f[N][N]; int main(){ int n; cin>>n; rep(i,n){ cin>>a[i]; b[i]=a[i]; } sort(b+1,b+1+n); int m=unique(b+1,b+1+n)-b-1; rep(i,n){ LL res=inf; rep(j,m){ res=min(res,f[i-1][j]); f[i][j]=res+abs(a[i]-b[j]); } } LL ans=inf; rep(i,m)if(f[n][i]<ans)ans=f[n][i]; cout<<ans; }
02:46(1A)