codeforces802 A-O Helvetic Coding Contest 2017 online mirror 

 

A  Heidi and Library (easy)

水题 同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;
}

  

B  Heidi and Library (medium)

经典的内存管理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;
}

  

Heidi and Library (hard)

费用流

考虑最暴力的方法,每次调用新的书都直接购买,这多半不是最优解。

有没有办法优化到最优解呢?

考虑对于书架上的每一个位置,让它在恰当的时候继续持有书,在恰当的时候购买新的书,这样我们就能找到最优解了。

对于相同的书,连一条费用为-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;
}

  

Marmots (easy)

根据泊松分布的特点,对称轴两边的概率密度最大。

用这个特点来判断是泊松分布还是平均分布。

#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;
}

  

Marmots (medium)

在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;
}

  

Marmots (hard)

由于出现了负数,所以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;	
}

  

Fake News (easy)

公共子序列问题 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;
}

  

Fake News (medium)

很简单的一道计数题 直接看代码吧~

#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");
}

  

Fake News (hard)

后缀数组+记忆化搜索

很有趣的一道后缀数组题

题目的意思就是让我们求一个字符串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;
}

  

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;
}

  

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;
}

  

 

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;
}

  

April Fools' Problem (easy)

每个序列排序后前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;
}

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;
}

  

April Fools' Problem (hard)

题意和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;
}

  

posted on 2017-05-28 20:14  Bingsen  阅读(432)  评论(0编辑  收藏  举报