codeforces802 A-O Helvetic Coding Contest 2017 online mirror
水题 同B
#include<cstdio> #include<cstdlib> #include<cstring> using namespace std; const int maxn=1000000; int n,k,a[maxn],num; bool ex[maxn],need[maxn]; int main() {//freopen("t.txt","r",stdin); scanf("%d%d",&n,&k); num=0; memset(ex,0,sizeof(ex)); for(int i=0;i<n;i++) scanf("%d",&a[i]); int ans=0; for(int i=0;i<n;i++) { if(ex[a[i]]) continue; if(num<k) { num++; ans++; ex[a[i]]=true; } else { int sum=0; memset(need,0,sizeof(need)); for(int j=i+1;j<n&&sum<k-1;j++) { if(!ex[a[j]])continue; if(need[a[j]]==false)sum++; need[a[j]]=true; } for(int j=1;j<=n;j++) { if(ex[j]&&(!need[j])){ex[j]=false;ex[a[i]]=true;ans++;break;} } } } printf("%d\n",ans); return 0; }
经典的内存管理OPT算法
不过基本没有实际应用价值,因为操作系统不可能知道之后要调用哪些内存。
用map或者堆都可以实现 堆会快很多
map版本
#include <bits/stdc++.h> using namespace std; set<int> s; int n,cs,k,c[400400],ne[400400],la[400400],cc; int main(){ scanf("%d %d",&n,&k); for(int i=0;i<n;i++)scanf("%d",&c[i]); for(int i=n-1;i>=0;i--){ if(!la[c[i]])ne[i]=1e6,la[c[i]]=i; else ne[i]=la[c[i]],la[c[i]]=i; } for(int i=0;i<n;i++){ if(s.count(i)){s.erase(i),s.insert(ne[i]);continue;} if((int)s.size()<k) s.insert(ne[i]),cc++; else s.erase(--s.end()),s.insert(ne[i]),cc++; } printf("%d\n",cc); }
优先队列版本
#include<cstdio> #include<cstdlib> #include<cstring> #include<stack> #include<deque> #include<queue> using namespace std; const int maxn=500000; int n,k,a[maxn],num,ne[maxn],ls[maxn]; bool ex[maxn]; priority_queue<int>que; int main() {//freopen("t.txt","r",stdin); while(!que.empty())que.pop(); scanf("%d%d",&n,&k); num=0; memset(ex,0,sizeof(ex)); for(int i=0;i<n;i++)scanf("%d",&a[i]); for(int i=n-1;i>=0;i--) { if(!ls[a[i]])ls[a[i]]=1e+8; ne[i]=ls[a[i]]; ls[a[i]]=i; } int ans=0; int j=1; int maxv=0,max2v=0; for(int i=0;i<n;i++) { if(ex[i]) { while(que.size()>0&&que.top()<=i)que.pop(); ex[i]=false; if(ne[i]<=n)ex[ne[i]]=true; que.push(ne[i]); continue; } if(num<k) { num++; ans++; if(ne[i]<=n)ex[ne[i]]=true; que.push(ne[i]); } else { int nowv=que.top(); que.pop(); ans++; if(nowv<=n)ex[nowv]=false; if(ne[i]<=n)ex[ne[i]]=true; que.push(ne[i]); } } printf("%d\n",ans); return 0; }
费用流
考虑最暴力的方法,每次调用新的书都直接购买,这多半不是最优解。
有没有办法优化到最优解呢?
考虑对于书架上的每一个位置,让它在恰当的时候继续持有书,在恰当的时候购买新的书,这样我们就能找到最优解了。
对于相同的书,连一条费用为-c[]的边(持有即相当于不用买新的所以答案-c[]),不同的书连一条费用为0的边.
#include<bits/stdc++.h> using namespace std; typedef long long LL; #define N 200020 const LL INF = 1e9; int nxt[N], cost[N], cap[N], to[N], head[N], cnt; void init(){ memset(head, -1, sizeof head); } void add_Edge(int S, int T, int c, int w){ nxt[cnt] = head[S], to[cnt] = T, cap[cnt] = c, cost[cnt] = w, head[S] = cnt ++; nxt[cnt] = head[T], to[cnt] = S, cap[cnt] = 0, cost[cnt] = -w, head[T] = cnt ++; } int prv[N], vis[N]; LL dist[N]; LL SPFA(int S, int T, int vet){ queue <int> Q; fill(dist, dist + vet, INF); fill(prv, prv + vet, -1); dist[S] = 0, Q.push(S), vis[S] = true; while(!Q.empty() ){ int x = Q.front(); Q.pop(), vis[x] = false; for(int id = head[x]; ~id; id = nxt[id]) if( cap[id] ){ int y = to[id]; if(dist[y] > dist[x] + cost[id]){ dist[y] = dist[x] + cost[id]; prv[y] = id; if(!vis[y]) Q.push(y), vis[y] = true; } } } if(!~prv[T]){ return INF; } int cur = T; while( cur != S ) { cur = prv[cur]; cap[cur] --; cap[cur xor 1] ++; cur = to[cur xor 1]; } return dist[T]; } int a[N], c[N], n, m; int main(){ //freopen("t.txt", "r", stdin); scanf("%d %d", &n, &m); for(int i = 1; i <= n; i ++) scanf("%d", a + i); for(int i = 1; i <= n; i ++) scanf("%d", c + i); init(); LL ans = 0; int S = n + 1, T = 2 * n + 2; for(int i = 1; i <= n; i ++){ ans += c[a[i]]; add_Edge(S, i, 1, 0); add_Edge(i, S + i, 1, -INF); for(int j = i + 1; j <= n; j ++){ if(a[i] == a[j]) add_Edge(i + S, j, 1, -c[a[j]]); else add_Edge(i + S, j, 1, 0); } add_Edge(i + S, T, 1, 0); } for(int step = 1; step <= m; step ++){ LL tmp = SPFA(S, T, T + 1); if(tmp >= 0) break; ans += tmp; } cout << ans + INF * n << endl; }
根据泊松分布的特点,对称轴两边的概率密度最大。
用这个特点来判断是泊松分布还是平均分布。
#include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> using namespace std; int a[250],b[250]; int main() { int T; scanf("%d",&T); while(T--) { for(int i=0;i<250;i++)scanf("%d",&a[i]); //for(int i=0;i<250;i++)scanf("%d",&b[i]); int mina=a[0],maxa=a[0],minb=b[0],maxb=b[0]; double mida=0; for(int i=0;i<250;i++) { mida+=a[i]; //minb=min(minb,b[i]);maxb=max(maxb,b[i]); } mida/=250.; int sum=0,sumb=0; double len=mida/2; for(int i=0;i<250;i++) { if(a[i]>(mida-len)&&a[i]<(mida+len))sum++; if(a[i]<=1)sumb++; } if(sum<180||sumb>3)printf("uniform\n"); else printf("poisson\n"); } return 0; }
在D的基础上,首先判断是 泊松分布还是平均分布
如果是泊松分布求所有值的平均值,否则求最大值和最小值的平均值。
#include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> using namespace std; int a[250],b[250]; int main() { int T; scanf("%d",&T); while(T--) { for(int i=0;i<250;i++)scanf("%d",&a[i]); //for(int i=0;i<250;i++)scanf("%d",&b[i]); double mina=a[0],maxa=a[0],minb=b[0],maxb=b[0]; double mida=0; for(int i=0;i<250;i++) { mida+=a[i]; mina=min(mina,(double)a[i]);maxa=max(maxa,(double)a[i]); } mida/=250.; int sum=0,sumb=0; double len=mida/2; for(int i=0;i<250;i++) { if(a[i]>(mida-len)&&a[i]<(mida+len))sum++; if(a[i]<=1)sumb++; } if(sum<180||sumb>3)//printf("uniform\n"); { printf("%.0lf\n",(mina+maxa)/2+0.5); } else //printf("poisson\n"); { printf("%.0lf\n",mida+0.5); } } return 0; }
由于出现了负数,所以D中简单粗暴的方法不可取了。
不过数据并没有变复杂,由于平均分布相对于泊松分布更加离散,所以方差会有明显的区别。
利用方差来判断,就不怕负数了。
#include<bits/stdc++.h> #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,a,b) for (int i=a;i>=b;i--) using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0'||ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0'&&ch<='9') {x=x*10+(ch^48); ch=getchar();} return x*f; } const int N = 251; const int P = 1005; const double e = 2.718281828459045235360287471352; int a[N]; double tp1[P<<1],tp2[P<<1]; int main() { int T=read(); while (T--) { int mx=0; double mean=0; rep(i,1,250) a[i]=read(),mean+=a[i],mx=max(mx,a[i]); double D=0; mean/=250; rep(i,1,250) D+=a[i]*a[i]; D/=(double)250; D-=mean*mean; double sigma = sqrt(D); if (mx/sigma<=1.9) puts("uniform"); else puts("poisson"); } return 0; }
公共子序列问题 O(N^2)
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<string> #include<vector> #include<cstring> using namespace std; int dp[1000][1000]; int LCS(int n1,int n2,string s1,string s2) { for(int i=0;i<n1;i++) for(int j=0;j<n2;j++) { if(i>0)dp[i][j]=dp[i-1][j]; if(j>0&&dp[i][j-1]>dp[i][j])dp[i][j]=dp[i][j-1]; if(s1[i]==s2[j]) { if(i==0||j==0)dp[i][j]=1; else dp[i][j]=dp[i-1][j-1]+1; } } return dp[n1-1][n2-1]; } int main() {//freopen("t.txt","r",stdin); ios::sync_with_stdio(false); string s1,s2; s2="heidi"; while(cin>>s1) { memset(dp,0,sizeof(dp)); int len=LCS(s1.length(),s2.length(),s1,s2); if(len==5)printf("YES\n"); else printf("NO\n"); s1.clear();s2.clear(); } return 0; }
很简单的一道计数题 直接看代码吧~
#include<cstdio> #include<algorithm> using namespace std; int Comb[110][6], C[110], S; int main(){ int i, j; for(i=0;i<=45;i++){ Comb[i][0]=1; for(j=1;j<=5&&j<=i;j++)Comb[i][j]=Comb[i-1][j]+Comb[i-1][j-1]; } scanf("%d",&S); for(i=45;i>=5;i--){ while(S>=Comb[i][5]){ C[i]++; S-=Comb[i][5]; } } for(i=0;i<45;i++){ while(C[i]--)printf("b"); printf("a"); } printf(" aaaaab\n"); }
后缀数组+记忆化搜索
很有趣的一道后缀数组题
题目的意思就是让我们求一个字符串L所有子串在这个串中出现的次数。
这种问题用脚指头想都知道肯定要上后缀数组啦!
对于数组height[l...r] 其中的最小值就是他们的公共前缀的长度,而这个公共前缀就是L的一个唯一的子串。r-l+1就是它出现的次数。(ps:不懂height数组的自行学习后缀数组再来看)
怎样高效统计呢?
对于height[0,len(L)-1]我们能否高效的找到它的最小值?可以。
那么假设最小值的位置是mid 然后我们把height分成l..mid mid+1....r分别计算,那么怎么合并呢?
两个子片段的公共最小Height值就是他们的公共前缀长度,我们可以知道这个公共前缀出现的次数,但是同时要排除他们在字串中贡献的值。
太难描述了,具体转移方法看代码吧。
注意,mid一定是最小值所在的位置,不可以随意划分height数组,那样是错的。
求mid不能太暴力,会TLE。
#include <iostream> #include <cstring> #include <cstdio> #include<vector> using namespace std; const int MAX = 100500; const int nMAX = 105; const int mMAX = 1005; int strnum; char str[MAX]; int source[MAX]; int sa[MAX], rk[MAX], height[MAX]; int wa[MAX], wb[MAX], wv[MAX], wd[MAX]; bool vis[nMAX]; int id[MAX]; int anslen, anspos[mMAX], ansnum; const int MAXN=200000+100; void radix(int *str,int *a,int *b,int n,int m) { static int count[MAXN]; memset(count,0,sizeof(count)); for(int i=0;i<n;++i)++count[str[a[i]]]; for(int i=1;i<=m;++i)count[i]+=count[i-1]; for(int i=n-1;i>=0;--i)b[--count[str[a[i]]]]=a[i]; } void sorted_suffix_array(int *str,int *sa,int n,int m) { static int rank[MAXN],a[MAXN],b[MAXN]; for(int i=0;i<n;++i)rank[i]=i; radix(str,rank,sa,n,m); rank[sa[0]]=0; for(int i=1;i<n;++i)rank[sa[i]]=rank[sa[i-1]]+(str[sa[i]]!=str[sa[i-1]]); for(int i=0;(1<<i) <n;++i) { for(int j=0;j<n;++j) { a[j]=rank[j]+1; b[j]=j+(1<<i)>=n? 0:rank[j+(1<<i)]+1; sa[j]=j; } radix(b,sa,rank,n,n); radix(a,rank,sa,n,n); rank[sa[0]]=0; for(int j=1;j<n;++j) { rank[sa[j]]=rank[sa[j-1]]+(a[sa[j-1]]!=a[sa[j]]||b[sa[j-1]]!=b[sa[j]]); } } } void calc_height(int *str,int *sa,int *h,int n) { static int Rank[MAXN]; int k=0; h[0]=0; for(int i=0;i<n;++i)Rank[sa[i]]=i; for(int i=0;i<n;++i) { k= k==0?0:k-1; if(Rank[i]!=0) while(str[i+k]==str[sa[Rank[i]-1]+k])++k; h[Rank[i]]=k; } } int stlen; long long dp(long long l,long long r,long long &summ,vector<int>&nemi,int flag) { if(l==r){summ=(long long)stlen-(long long)sa[l];return summ;} int mid=-1; vector<int>nemr; if(flag!=-1)mid=nemi[flag]-1; else { nemi.push_back(l+1); for(int i=l+1;i<r;i++) { if(height[i+1]<height[nemi[(int)nemi.size()-1]])nemi.push_back(i+1); } flag=(int)nemi.size()-1; mid= nemi[flag]-1; } long long sum1,sum2; long long int minh=min(min(dp(l,mid,sum1,nemi,flag-1),dp(mid+1,r,sum2,nemr,-1)),(long long)height[mid+1]); summ=sum1+sum2+minh*(r-l+1)*(r-l+1)-minh*(mid-l+1)*(mid-l+1)-minh*(r-mid)*(r-mid); nemr.clear(); return minh; } long long int solve(char *st) { stlen=strlen(st); for(int i=0;i<stlen;i++) source[i]=st[i]-'a'+1; sorted_suffix_array(source,sa,(int)stlen,126); calc_height(source,sa,height,(int)stlen); height[0]=1e+8; long long int ans=0; vector<int>mi; mi.clear(); dp(0,stlen-1,ans,mi,-1); return ans; } int main() {//freopen("t.txt","r",stdin); int T; scanf("%d",&T); while(T--) { scanf("%s",&str); printf("%I64d\n",solve(str)); } return 0; }
J Send the Fool Further! (easy)
水题
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<string> #include<vector> #include<cstring> using namespace std; vector<int>adj[200]; int w[200][200]; int dist[200]; void dfs(int cur,int fa,int len) { for(int i=0;i<adj[cur].size();i++) { int ne=adj[cur][i]; if(ne==fa)continue; dist[ne]=min(dist[ne],len+w[cur][ne]); dfs(ne,cur,len+w[cur][ne]); } } int main() {//freopen("t.txt","r",stdin); int n; scanf("%d",&n); int u,v; for(int i=0;i<n-1;i++) { scanf("%d%d",&u,&v); scanf("%d",&w[u][v]); w[v][u]=w[u][v]; adj[u].push_back(v); adj[v].push_back(u); } for(int i=1;i<n;i++) dist[i]=99999999; dist[0]=0; dfs(0,-1,0); int ans=0; for(int i=0;i<n;i++) ans=max(ans,dist[i]); printf("%d\n",ans); return 0; }
K Send the Fool Further! (medium)
比较有趣的树形DP
考虑最优解,必有两种情况,要么在0结束,要么不在0结束。对于其他节点也是一个道理。
所以设dp[i][bool]为从i出发,的最优解,bool为0则最后回到i否则不用回到i
转移方程直接看程序吧 很简单。
#include <bits/stdc++.h> using namespace std; const int maxn = 200000; int dp[maxn][2]; vector< pair<int,int> > E[maxn]; int n,k; bool cmp(const pair<int,int> &a, const pair<int,int> &b){ return (a.first > b.first); } void dfs(int u, int p){ vector< pair<int,int> > c; set<int> st; for(auto e: E[u]){ int v = e.first; int cst = e.second; if(v == p) continue; dfs(v,u); c.push_back({dp[v][0] + cst,v}); } sort(c.begin(), c.end(), cmp); int tk = min(k-1, (int)(c.size())); for(int i = 0; i < tk; i++){ dp[u][0] += c[i].first; st.insert(c[i].second); } int extra = 0; if(tk != (int)(c.size())) extra = c[tk].first; for(auto e: E[u]){ int v = e.first; int cst = e.second; if(v == p) continue; if(st.count(v) == 0) dp[u][1] = max(dp[u][1], dp[v][1] + dp[u][0] + cst); else dp[u][1] = max(dp[u][1], dp[u][0] - dp[v][0] + extra + dp[v][1]); } st.clear(); c.clear(); } int main(){ scanf("%d%d", &n, &k); for(int i = 1; i < n; i++){ int u,v,c; scanf("%d%d%d", &u, &v, &c); E[u].push_back({v,c}); E[v].push_back({u,c}); } dfs(0,-1); cout << max(dp[0][0], dp[0][1]) << "\n"; return 0; }
L Send the Fool Further! (hard)
这道题题意说的不是很清楚 总的来说是让我们求E(0)
E(0)和E(v)有线性关系,v是0的孩子。
所以,暴力解方程组的方法是可以求出来的。但是复杂度太高O(n^3)
有没有聪明一点的方法呢?
还是考虑E(0)和E(v)的关系
E(v)由它的孩子和E(0)线性组合而成。假设E(v)不考虑0的情况下期望为G(v)G(v)可以在dfs()的过程中求出来。
我们通过一定的代数变形可以直接由G(v)推出E(v)
嗯。。大致就是这样。。还是那句话 近世代数太重要了。
代码很清晰
直观的看的话 在树上求解问题,最关键的就是找出递归关系,
也就是对于当前节点求解(做到)不需考虑它的父亲部分(这样我们离答案就很近了因为最后的答案G【0】就是不需要考虑父亲的)。
#include <cstdio> #include <cstdlib> #include <algorithm> #include <vector> #include <string> #define SIZE 100005 #define MOD 1000000007 using namespace std; typedef long long int ll; struct edge { int to,cost; edge(int to=0,int cost=0):to(to),cost(cost){} }; vector <edge> vec[SIZE]; ll F[SIZE],G[SIZE]; int nd[SIZE]; int n; ll mpow(ll m,ll t) { if(t==0) return 1LL; ll ret=mpow(m*m%MOD,t/2); if(t%2==1) ret=ret*m%MOD; return ret; } ll inv(ll m) { return mpow(m,MOD-2); } void dfs(int v=0,int p=-1) { if(vec[v].size()==1) { F[v]=G[v]=0; return; } ll sumG=0,sumF=vec[v].size(); for(int i=0;i<vec[v].size();i++) { edge e=vec[v][i]; sumG+=e.cost; if(sumG>=MOD) sumG-=MOD; if(e.to!=p) { dfs(e.to,v); sumG+=G[e.to]; if(sumG>=MOD) sumG-=MOD; sumF-=F[e.to]; if(sumF<0) sumF+=MOD; } } ll g=inv(sumF); F[v]=g; G[v]=g*sumG%MOD; } int main() { freopen("t.txt","r",stdin); scanf("%d",&n); for(int i=0;i<n-1;i++) { int a,b,c; scanf("%d %d %d",&a,&b,&c); vec[a].push_back(edge(b,c)); vec[b].push_back(edge(a,c)); } dfs(); printf("%lld\n",G[0]); return 0; }
每个序列排序后前k个数的和
#include<cstdio> #include<cstdlib> #include<algorithm> using namespace std; int num[3000]; int main() {//freopen("t.txt","r",stdin); int n,m,t; int mins=-1; scanf("%d%d",&n,&m); for(int i=0;i<n;i++) { scanf("%d",&num[i]); } sort(num,num+n); int ans=0; for(int i=0;i<m;i++) ans+=num[i]; printf("%d\n",ans); return 0; }
N April Fools' Problem (medium)
很有趣的一道贪心题。
给定两个正整数序列A和B
求两个A和B的长度为k的子序列 a和b满足 a中的第i个元素在A中的位置<= b中的第i个元素在B中的位置 并且和最小。
用贪心的方法,先求一个最小的合法解 a[i]和b[j]且 j>=i,然后他们之间[i...j]就可以放反向的数对了也就是在找最小解的时候可以允许j<i了 。
#include<bits/stdc++.h> using namespace std; typedef long long int LL ; const int maxn=3000; LL a[maxn],b[maxn],va[maxn],vb[maxn],verse[maxn]; int n,k; int main() {//freopen("t.txt","r",stdin); scanf("%d%d",&n,&k); for(int i=0;i<n;i++)scanf("%I64d",&a[i]); for(int i=0;i<n;i++)scanf("%I64d",&b[i]); LL ans=0; b[n]=1e+18; while(k--) {int ra=-1,rb; for(int i=n-1,j=n,minb=n;i>=0;i--) { if(j>i)j=i; if(b[j]<b[minb]&&!vb[j])minb=j; while((j-1)>=0&&verse[j-1])if(b[--j]<b[minb]&&!vb[j])minb=j; if(!va[i]&&(ra==-1||a[i]+b[minb]<a[ra]+b[rb]))ra=i,rb=minb; } ans+=a[ra]+b[rb];va[ra]++;vb[rb]++; for(int i=ra;i<rb;i++)verse[i]++; for(int i=rb;i<ra;i++)verse[i]--; } printf("%I64d\n",ans); return 0; }
题意和N一样 数据提高到了500000 非常有趣的一道优化题
显然N中我们O(n^2)的算法要TLE
有什么办法优化到O(nlogn)么?
1.首先考虑当前已经选定了N个题目以及他们合法的打印时间,对于以后可选的打印时间,可以用来优化答案。O(n)
2.那么主要问题就变成了如何选取恰当的N个初始题目。
我们用二分的方法选取一个阀值mid 比它更优的 我们才让它成为备选题目,这样不断二分mid 总会找到一个最恰当的mid使得选中的题目正好是m个最优的。
当然如果出现比m多的情况说明他们是相等的题目和打印时间。
看代码吧 很清晰。
#include <bits/stdc++.h> using namespace std; typedef long long LL; #define N 500050 priority_queue <LL, vector <LL>, greater<LL> > Qa; priority_queue <LL> Qb; LL a[N], b[N]; int n, m; const LL INF = 1e13; int main() { // freopen("in.txt", "r", stdin); scanf("%d %d", &n, &m); for (int i = 1; i <= n; i ++) scanf("%I64d", a + i); for (int i = 1; i <= n; i ++) scanf("%I64d", b + i); LL st = 0, en = INF, ans = 0; while (st <= en ) { LL mid = (st + en) >> 1; while ( !Qa.empty() ) Qa.pop(); while ( !Qb.empty() ) Qb.pop(); LL tmp = 0; int sz = 0; for (int i = 1; i <= n; i ++) { Qa.push(a[i]); LL tmp1 = Qa.top() + b[i] - mid; LL tmp2 = Qb.empty() ? INF : b[i] - Qb.top(); if (tmp1 <= tmp2 && tmp1 <= 0) { tmp += tmp1; sz ++; Qb.push(b[i]); Qa.pop(); } else if (tmp2 < tmp1 && tmp2 < 0){ tmp += tmp2; Qb.pop(); Qb.push(b[i]); } } if (sz >= m) { ans = tmp + m * mid; en = mid - 1; } else { st = mid + 1; } } cout << ans << endl; }