NOIP2017模拟赛总结(2017.10.26-2017.10.29)

嗯,这次是10.26到10.29的题,因为10.27休息了一天,所以还是3天。

2017.10.26 Problem A
题目大意: 有一个容积为 V V V的背包和 n n n个物品,每个物品有两个参数 a i , b i a_i,b_i ai,bi,表示要装下这个物品需要消耗 a i a_i ai的容积,但装进去后背包的容积会扩大 b i b_i bi,问存不存在一种装物品的顺序使得能装下所有物品。
做法: 本题需要用到贪心。
首先对于 a i ≤ b i a_i\le b_i aibi的物品,按照 a i a_i ai从小到大的顺序取,如果不能取完那就无解,关键是如何处理 a i > b i a_i>b_i ai>bi的物品。
实际上,像这类取东西的题目,可以有一类贪心证法,这里记录下我的证法。
注意到无论按什么顺序取两个物品,取完后的状态都是一样的,那么我们就试图证明,如果取完物品 i i i后还能取物品 j j j(条件1),一定可以推出取完物品 j j j后还能取物品 i i i(条件2),那么先取物品 j j j就是更优的。为什么呢?因为在这种情况下,条件1,2满足的情况只有三种:两个都满足,两个都不满足,条件1不满足而条件2满足。显然情况1,2对结果没有影响,关键是情况3,若是这种情况的话,先取 i i i就一定不能取完所有物品,而先取 j j j说不定可以,所以先取 j j j一定最优。
我们把条件1和条件2用数学式子表达出来:
条件1: V − a i + b i ≥ a j V-a_i+b_i\ge a_j Vai+biaj
条件2: V − a j + b j ≥ a i V-a_j+b_j\ge a_i Vaj+bjai
在两个条件中 V V V的解集分别为 V ≥ a i + a j − b i V\ge a_i+a_j-b_i Vai+ajbi V ≥ a i + a j − b j V\ge a_i+a_j-b_j Vai+ajbj,要使条件1能推出条件2,那么条件2的解集应包含条件1的解集,即 a i + a j − b j ≤ a i + a j − b i a_i+a_j-b_j\le a_i+a_j-b_i ai+ajbjai+ajbi,解得 b i ≤ b j b_i\le b_j bibj,由此证得先取 b i b_i bi较大的物品是最优的,那么就这么取即可。时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,T;
ll v;
struct point {ll a,b;} f[100010],g[100010];

bool cmp0(point a,point b)
{
	return a.a<b.a;
}

bool cmp(point a,point b)
{
	return a.b>b.b;
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%lld",&n,&v);
		int t1=0,t2=0;
		for(int i=1;i<=n;i++)
		{
			ll a,b;
			scanf("%lld%lld",&a,&b);
			if (b-a>=0) g[++t1].a=a,g[t1].b=b;
			else f[++t2].a=a,f[t2].b=b;
		}
		
		sort(g+1,g+t1+1,cmp0);
		sort(f+1,f+t2+1,cmp);
		bool flag=1;
		for(int i=1;i<=t1;i++)
		{
			if (v<g[i].a) {flag=0;break;}
			v=v-g[i].a+g[i].b;
		}
		if (!flag) {printf("No\n");continue;}
		for(int i=1;i<=t2;i++)
		{
			if (v<f[i].a) {flag=0;break;}
			v=v-f[i].a+f[i].b;
		}
		if (flag) printf("Yes\n");
		else printf("No\n");
	}
	
	return 0;
}

2017.10.26 Problem B
题目大意: 有一个长为 n n n的数列,若一个区间 [ L , R ] [L,R] [L,R]内存在一个数 a k a_k ak,使得对于所有 L ≤ i ≤ R L\le i\le R LiR,都有 a k ∣ a i a_k|a_i akai,那么我们说这个区间是合法的,价值为 R − L R-L RL,求价值最大区间的价值以及所有这些区间的左端点。
做法: 本题需要用到枚举。
对于一个点,我们可以暴力求出以它为公因数的最长合法区间的左端点和右端点,这样做的时间复杂度是 O ( n 2 ) O(n^2) O(n2)的,虽然常数不大,但如果所有数字都相同的话会被卡掉。注意到,如果当前可以扩展到 r h t rht rht这个右端点,那么从这个点到这个右端点之间的这些点实际上求了也没用,那么我们可以直接跳到 r h t + 1 rht+1 rht+1继续暴力。可以证明最坏情况下时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn),随机情况下复杂度为 O ( n ) O(n) O(n)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,lft[500010]={0},rht[500010]={0},mx=0,num=0;
ll a[500010];
bool vis[500010]={0};

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	
	for(int i=1;i<=n;i=rht[i]+1)
	{
		int j;
		for(j=i;j>=1;j--)
			if (a[j]%a[i]!=0) break;
		lft[i]=j+1;
		for(j=i;j<=n;j++)
			if (a[j]%a[i]!=0) break;
		rht[i]=j-1;
		mx=max(mx,rht[i]-lft[i]);
	}
	
	for(int i=1;i<=n;i++)
		if (rht[i]-lft[i]==mx) vis[lft[i]]=1;
	for(int i=1;i<=n;i++)
		if (vis[i]) num++;
	printf("%d %d\n",num,mx);
	for(int i=1;i<=n;i++)
		if (vis[i]) printf("%d ",i);
	
	return 0;
}

2017.10.26 Problem C
题目大意: 一个 n ( ≤ 30 ) × m ( ≤ 30 ) n(\le 30)\times m(\le 30) n(30)×m(30)的棋盘中,要放 c ( ≤ 10 ) c(\le 10) c(10)种颜色的棋子,每种颜色的棋子数量可能不同,但总数不超过 900 900 900,不同颜色的棋子不能放在同一行或同一列,求合法的方案数。
做法: 本题需要用到组合数学递推+二维背包。
观察发现,每种颜色的棋子都独占若干行和若干列,如果我们能知道在正好占用若干行若干列时,放若干个棋子的方案数的话,就可以做二维背包了!令 f ( i , j , k ) f(i,j,k) f(i,j,k) k k k个同色棋子正好占用 i i i j j j列的方案数,那么可得状态转移方程:
f ( i , j , k ) = C i × j k − ∑ p = 0 i ∑ q = 0 j C i p C j q f ( i − p , j − q , k ) f(i,j,k)=C_{i\times j}^k-\sum_{p=0}^i\sum_{q=0}^jC_i^pC_j^qf(i-p,j-q,k) f(i,j,k)=Ci×jkp=0iq=0jCipCjqf(ip,jq,k)
上式是怎么推出来的呢?可以看做,用在 i × j i\times j i×j个格子里放 k k k个棋子的方案数,减去正好空 p p p q q q列的方案数。注意到第三维实际上没有必要枚举全部 1 1 1~ 900 900 900,只要求出 k = k= k=某种棋子颜色总数的情况就可以了,那么我们删去第三维,上式就是 O ( c n 2 m 2 ) O(cn^2m^2) O(cn2m2)的了,可以接受。
接下来就是二维背包了。令 g ( k , i , j ) g(k,i,j) g(k,i,j)为放前 k k k种颜色,还剩 i i i j j j列没有占用的方案数,那么有状态转移方程:
g ( k , i , j ) = ∑ p = i n ∑ q = j m C p i C q j f ( p − i , q − j , n u m ( k ) ) g ( k − 1 , p , q ) g(k,i,j)=\sum_{p=i}^n\sum_{q=j}^mC_p^iC_q^jf(p-i,q-j,num(k))g(k-1,p,q) g(k,i,j)=p=inq=jmCpiCqjf(pi,qj,num(k))g(k1,p,q)
其中 n u m ( k ) num(k) num(k)为第 k k k种颜色的棋子数量,边界条件为 g ( 0 , n , m ) = 1 g(0,n,m)=1 g(0,n,m)=1,显然答案为 ∑ i = 1 n ∑ j = 1 m g ( c , i , j ) \sum_{i=1}^n\sum_{j=1}^m g(c,i,j) i=1nj=1mg(c,i,j)。上述的方程应该可以挺容易推出来了,时间复杂度为 O ( c n 2 m 2 ) O(cn^2m^2) O(cn2m2),那么预处理出组合数就可以通过该题了。
(原题好像叫做CQOI2011放棋子)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define mod 1000000009
using namespace std;
int n,m,c,num[11],tot=0;
ll cc[1010][1010],f[35][35]={0},g[2][35][35]={0};

ll power(ll a,ll b)
{
	ll s=1,ss=a;
	while(b)
	{
		if (b&1) s=(s*ss)%mod;
		b>>=1;ss=(ss*ss)%mod;
	}
	return s;
}

ll C(int n,int m)
{
	if (m>n) return 0;
	return cc[n][m];
}

int main()
{
	scanf("%d%d%d",&n,&m,&c);
	for(int i=1;i<=c;i++) scanf("%d",&num[i]),tot+=num[i];
	
	cc[0][0]=1;
	for(int i=1;i<=1000;i++)
	{
		cc[i][0]=1;
		for(int j=1;j<=i;j++)
			cc[i][j]=(cc[i-1][j-1]+cc[i-1][j])%mod;
	}
	
	int now=1,past=0;
	g[past][n][m]=1;
	for(int k=1;k<=c;k++)
	{
		memset(f,0,sizeof(f));
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
			{
				f[i][j]=C(i*j,num[k]);
				for(int p=0;p<=i;p++)
					for(int q=0;q<=j;q++)
						if (p||q) f[i][j]=(((f[i][j]-((C(i,p)*C(j,q))%mod)*f[i-p][j-q])%mod)+mod)%mod;
			}
		for(int i=0;i<=n;i++)
			for(int j=0;j<=m;j++)
			{
				g[now][i][j]=0;
				for(int p=i;p<=n;p++)
					for(int q=j;q<=m;q++)
						g[now][i][j]=(g[now][i][j]+((((C(p,i)*C(q,j))%mod)*f[p-i][q-j])%mod)*g[past][p][q])%mod;
			}
		swap(now,past);
	}
	
	ll ans=0;
	for(int i=0;i<=n;i++)
		for(int j=0;j<=m;j++)
			ans=(ans+g[past][i][j])%mod;
	printf("%lld\n",ans);
	
	return 0;
}

2017.10.28 Problem A
题目大意: n ( ≤ 500000 ) × n n(\le 500000)\times n n(500000)×n个士兵占成 n × n n\times n n×n的方阵,第 i i i行第 j j j列的士兵站在 ( i , j ) (i,j) (i,j),所有士兵身高相同,问从 ( 0 , 0 ) (0,0) (0,0)能看到的士兵的数量。
做法: 本题需要用到欧拉函数。
题目显然是在求 ∑ i = 1 n ∑ j = 1 n [ gcd ⁡ ( i , j ) = 1 ] \sum_{i=1}^n\sum_{j=1}^n[\gcd(i,j)=1] i=1nj=1n[gcd(i,j)=1],推一下就可以发现答案为 1 + 2 ∑ i = 2 n φ ( i ) 1+2\sum_{i=2}^n\varphi(i) 1+2i=2nφ(i),线性筛求出欧拉函数然后求出这个式子就行了。
(然而我求稳写了 O ( n log ⁡ log ⁡ n ) O(n\log \log n) O(nloglogn)的筛法…)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
ll n,phi[500010],ans=0;
bool vis[500010]={0};

void calc_phi()
{
	for(int i=1;i<=n;i++) phi[i]=i;
	for(ll i=2;i<=n;i++)
		if (!vis[i])
		{
			for(ll j=1;i*j<=n;j++)
			{
				vis[i*j]=1;
				phi[i*j]=phi[i*j]*(i-1)/i;
			}
		}
}

int main()
{
	scanf("%lld",&n);
	calc_phi();
	for(int i=2;i<=n;i++) ans+=phi[i];
	ans=(ll)2*ans+1;
	printf("%lld",ans);
	
	return 0;
}

2017.10.28 Problem B
题目大意: n n n个炸弹,有些炸弹会连一条单向引线到另一个炸弹,引爆一个炸弹时,其引线连接的炸弹也会同时爆炸,引爆每个炸弹有一个非负分数,问引爆 k k k个炸弹最多能获得的分数是多少。
做法: 本题需要用到缩环+贪心+堆。
注意到一点,如果我们把没引出引线的点看做向点 0 0 0引了一条引线,那么整个图可以看做一棵树+若干棵环套树,注意到我们可以把环缩起来,并从缩起来的点向点 0 0 0连边,那么可以构成一个等价的图,而这个图显然是一棵树。那么问题变成,选取 k k k条到根的路径,使得这些路径所经过的点权之和最大。
然后我们就可以贪心了(我也不知道为什么能贪,写了个树形DP拍了几组数据发现结果都一样),先预处理出一个点子树中的点到该点能获得的最大点权和(也就是重链),还要记录提供最优答案的那个儿子(以下称为重儿子)。然后每次在堆里取最大的一个,然后顺着所取的路径,把路径上所有点的非重儿子的重链加入堆,取个 k k k次之后就能得出答案了,时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
int n,k,tot=0,first[400010]={0},vis[400010]={0},belong[200010],next[400010]={0};
ll a[200010],b[400010],f[400010],ans=0,mx,mxi;
struct edge {int v,next;} e[200010];
bool son[400010]={0};
struct point
{
	int id;
	ll val;
	bool operator < (point a) const
	{
		return val<a.val;
	}
};
priority_queue <point> Q;

ll read()
{
	ll s=0;
	char c;
	c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9')
	{
		s=s*10+c-'0';
		c=getchar();
	}
	return s;
}

void find_loop(int v)
{
	if (v==0) return;
	if (!vis[a[v]]) vis[a[v]]=vis[v],find_loop(a[v]);
	else if (vis[a[v]]==vis[v])
	{
		ll i=a[v];
		++tot;b[tot]=0;
		while(i!=v)
		{
			belong[i]=tot;
			i=a[i];
		}
		belong[v]=tot;
	}
}

void insert(int a,int b)
{
	e[++tot].v=b;
	e[tot].next=first[a];
	first[a]=tot;
}

void dfs(int v)
{
	f[v]=0;
	for(int i=first[v];i;i=e[i].next)
	{
		dfs(e[i].v);
		if (f[e[i].v]>f[v])
		{
			f[v]=f[e[i].v];
			next[v]=e[i].v;
		}
	}
	f[v]+=b[v];
}

int main()
{
	scanf("%d%d",&n,&k);
	belong[0]=0;
	for(int i=1;i<=n;i++)
	{
		a[i]=read(),b[i]=read();
		belong[i]=i;
	}
	
	tot=n;
	for(int i=1;i<=n;i++)
		if (!vis[i]) vis[i]=i,find_loop(i);
	tot=0;
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)
	{
		if (belong[i]>n) b[belong[i]]+=b[i];
		if (belong[i]==belong[a[i]]&&!vis[belong[i]]) vis[belong[i]]=1,insert(0,belong[i]);
		else if (belong[i]!=belong[a[i]]) insert(belong[a[i]],belong[i]);
	}
	
	dfs(0);
	point now,nex;
	now.id=0,now.val=f[0];
	Q.push(now);
	for(int i=1;i<=k;i++)
	{
		if (Q.empty()) break;
		now=Q.top(),Q.pop();
		ans+=now.val;
		int x=now.id;
		while(next[x])
		{
			for(int j=first[x];j;j=e[j].next)
				if (e[j].v!=next[x])
				{
					nex.id=e[j].v;
					nex.val=f[e[j].v];
					Q.push(nex);
				}
			x=next[x];
		}
	}
	printf("%lld",ans);
	
	return 0;
}

2017.10.28 Problem C
题目大意: 一个带边权的有向图,要从 u u u点走到 v v v点,,经过每个点要缴纳一个费用,每个点费用可能不同,一条路径的费用为路径上所有经过点的费用最大值,问在路径长度不超过 s s s的情况下,路径费用的最小值。
做法: 本题需要用到二分答案+最短路。
很显然需要二分费用,然后我们需要判定费用不超过一定值的时候存不存在长度不超过 s s s的路径,那么我们做一个最短路,不走费用超过该值的点即可。本题好像卡SPFA,所以我用Dijkstra算法写了,时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
int n,m,u,v,tot=0,first[10010]={0};
ll inf,s,f[10010],mxf=0,dis[10010];
struct edge {int v,next;ll d;} e[100010];
struct point
{
	int id;
	ll val;
	bool operator < (point a) const
	{
		return val>a.val;
	}
};
bool vis[10010];
priority_queue <point> Q;

void insert(int a,int b,ll d)
{
	e[++tot].v=b;
	e[tot].next=first[a];
	e[tot].d=d;
	first[a]=tot;
}

void dijkstra(ll limit)
{
	point now,next;
	while(!Q.empty()) Q.pop(); 
	for(int i=1;i<=n;i++)
	{
		if (i!=u) dis[i]=inf;
		else dis[i]=0;
	}
	if (f[u]>limit||f[v]>limit) return;
	
	for(int i=first[u];i;i=e[i].next)
		if (f[e[i].v]<=limit) dis[e[i].v]=min(dis[e[i].v],e[i].d);
	memset(vis,0,sizeof(vis));
	vis[u]=1;
	for(int i=1;i<=n;i++)
		if (i!=u&&f[i]<=limit)
		{
			now.id=i,now.val=dis[i];
			Q.push(now);
		}
	
	for(int i=2;i<=n;i++)
	{
		if (Q.empty()) return;
		now=Q.top(),Q.pop();
		while(vis[now.id])
		{
			if (Q.empty()) return;
			now=Q.top(),Q.pop();
		}
		vis[now.id]=1;
		for(int j=first[now.id];j;j=e[j].next)
			if (!vis[e[j].v]&&f[e[j].v]<=limit&&dis[e[j].v]>dis[now.id]+e[j].d)
			{
				next.id=e[j].v;
				next.val=dis[e[j].v]=dis[now.id]+e[j].d;
				Q.push(next);
			}
	}
}

int main()
{
	inf=1000000000;
	inf*=inf;
	
	scanf("%d%d%d%d%lld",&n,&m,&u,&v,&s);
	for(int i=1;i<=n;i++) scanf("%lld",&f[i]),mxf=max(mxf,f[i]);
	for(int i=1;i<=m;i++)
	{
		int a,b;ll d;
		scanf("%d%d%lld",&a,&b,&d);
		insert(a,b,d),insert(b,a,d);
	}
	
	dijkstra(inf);
	if (dis[v]>s) {printf("-1");return 0;}
	ll l=0,r=mxf;
	while(l<r)
	{
		ll mid=(l+r)/2;
		dijkstra(mid);
		if (dis[v]>s) l=mid+1;
		else r=mid;
	}
	printf("%lld",l);
	
	return 0;
}

2017.10.29 Problem A
题目大意: n n n支球队相互比赛,每两支球队会打一场主场和一场客场,赢了计 3 3 3分,输了不计分,平局各计 1 1 1分,给出比赛的情况,求出最后分数最高的球队编号(可能有多个)。
做法: 模拟,裸的不行,不讲了。
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,mx=-1,score[110]={0},mxi[110],t=0;
char s[110];

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		for(int j=1;j<=n;j++)
		{
			if (s[j-1]=='W') score[i]+=3;
			if (s[j-1]=='D') score[i]+=1,score[j]+=1;
			if (s[j-1]=='L') score[j]+=3;
		}
	}
	
	for(int i=1;i<=n;i++)
	{
		if (score[i]==mx) mxi[++t]=i;
		if (score[i]>mx) mx=score[i],t=1,mxi[t]=i;
	}
	
	for(int i=1;i<=t;i++)
		printf("%d ",mxi[i]);
	
	return 0;
}

2017.10.29 Problem B
题目大意: 平面上有 n ( ≤ 1000 ) n(\le 1000) n(1000)个点,记横坐标最小和最大的点为 A A A B B B,现在要从 A A A走到 B B B再走回 A A A,并经过每个点一次且仅一次( A A A两次),并且从 A A A B B B走时只能向横坐标更大的点走,从 B B B A A A走时只能向横坐标更小的点走,并且要求从 A A A B B B走时一定经过点 b 1 b_1 b1,从 B B B A A A走时一定经过点 b 2 b_2 b2,问满足要求的最短路径长度。
做法: 本题需要用到DP。
转化为双线程DP,令 f ( i , j ) f(i,j) f(i,j)为从 A A A走到 i i i,从 j j j走到 A A A,且从 A A A m a x ( i , j ) max(i,j) max(i,j)都已经走过的最短路径长度,我们发现 f ( i , j ) f(i,j) f(i,j) f ( max ⁡ ( i , j ) + 1 , j ) f(\max(i,j)+1,j) f(max(i,j)+1,j) f ( i , max ⁡ ( i , j ) + 1 ) f(i,\max(i,j)+1) f(i,max(i,j)+1)都可能有贡献,更新的式子为:
f ( max ⁡ ( i , j ) + 1 , j ) = min ⁡ ( f ( max ⁡ ( i , j ) + 1 , j ) , f ( i , j ) + d i s ( i , m a x ( i , j + 1 ) ) ) f(\max(i,j)+1,j)=\min(f(\max(i,j)+1,j),f(i,j)+dis(i,max(i,j+1))) f(max(i,j)+1,j)=min(f(max(i,j)+1,j),f(i,j)+dis(i,max(i,j+1)))
以上式子当 max ⁡ ( i , j ) + 1 ≠ b 1 \max(i,j)+1\ne b_1 max(i,j)+1=b1时可以进行更新。
f ( i , max ⁡ ( i , j ) + 1 ) = min ⁡ ( f ( i , max ⁡ ( i , j ) + 1 ) , f ( i , j ) + d i s ( j , m a x ( i , j + 1 ) ) ) f(i,\max(i,j)+1)=\min(f(i,\max(i,j)+1),f(i,j)+dis(j,max(i,j+1))) f(i,max(i,j)+1)=min(f(i,max(i,j)+1),f(i,j)+dis(j,max(i,j+1)))
以上式子当 max ⁡ ( i , j ) + 1 ≠ b 2 \max(i,j)+1\ne b_2 max(i,j)+1=b2时可以进行更新。
最后的答案为 f ( n , n ) f(n,n) f(n,n),时间复杂度为 O ( n 2 ) O(n^2) O(n2)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define inf 1000000000
using namespace std;
int n,b1,b2;
double x[1010],y[1010],dp[1010][1010];

double dis(int i,int j)
{
	return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}

int main()
{
	scanf("%d%d%d",&n,&b1,&b2);
	for(int i=0;i<n;i++)
		scanf("%lf%lf",&x[i],&y[i]);
	
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			dp[i][j]=inf;
	dp[0][0]=0;
			
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			if (i!=j||i==0)
			{
				int k=max(i,j)+1;
				if (i!=b1) dp[k][j]=min(dp[k][j],dp[i][j]+dis(i,k));
				if (j!=b2) dp[i][k]=min(dp[i][k],dp[i][j]+dis(j,k));
			}
	if (n-2!=b1) dp[n-1][n-1]=min(dp[n-1][n-1],dp[n-2][n-1]+dis(n-2,n-1));
	if (n-2!=b2) dp[n-1][n-1]=min(dp[n-1][n-1],dp[n-1][n-2]+dis(n-2,n-1));
	printf("%.2lf",dp[n-1][n-1]);
	
	return 0;
}

2017.10.29 Problem C
题目大意: 停车场有 n n n个车位,有 m m m个操作,每个操作是一辆车开入或者开出停车场,车开入之后会选择一个离最近的车最远的车位停入,如果有多个位置选择编号最小的,对于每个开入操作,输出它停放的车位编号。
做法: 本题需要用到链表+堆。
分析后发现,两个车位 i , j i,j i,j之间停放车辆时,最远的距离应是 ⌊ j − i 2 ⌋ − 1 \lfloor \frac{j-i}{2}\rfloor-1 2ji1(这里距离指两辆车之间相隔的车位数),这是一个定值,因此我们用一个链表维护车位被占用的情况,然后再用堆维护距离的最大值即可。时间复杂度 O ( m log ⁡ m ) O(m\log m) O(mlogm)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int n,m,pos[1000010],pre[500010],next[500010],val[500010],tot=2,nowtime=0;
int tim[500010]={0};
struct point
{
	int id,lft,val,t;
	bool operator < (point a) const
	{
		if (val!=a.val) return val<a.val;
		else return lft>a.lft;
	};
};
priority_queue <point> Q;

void insert(int x,int y)
{
	point now;
	val[++tot]=y;
	pre[next[x]]=tot;
	pre[tot]=x;
	next[tot]=next[x];
	next[x]=tot;
	now.id=x,now.lft=val[x],now.t=++nowtime;
	if (now.id==1||next[now.id]==2) now.val=val[next[now.id]]-val[now.id]-2;
	else now.val=(val[next[now.id]]-val[now.id])/2-1;
	Q.push(now);tim[now.id]=nowtime;
	now.id=tot,now.lft=y,now.t=++nowtime;
	if (now.id==1||next[now.id]==2) now.val=val[next[now.id]]-val[now.id]-2;
	else now.val=(val[next[now.id]]-val[now.id])/2-1;
	Q.push(now);tim[now.id]=nowtime;
}

void del(int x)
{
	point now;
	next[pre[x]]=next[x];
	pre[next[x]]=pre[x];
	now.id=pre[x],now.lft=val[pre[x]],now.t=++nowtime;
	if (now.id==1||next[now.id]==2) now.val=val[next[now.id]]-val[now.id]-2;
	else now.val=(val[next[now.id]]-val[now.id])/2-1;
	Q.push(now);tim[now.id]=nowtime;
	tim[x]=nowtime;
}

int main()
{
	scanf("%d%d",&n,&m);
	pre[2]=1,next[1]=2;
	val[1]=0,val[2]=n+1;
	point now;
	now.id=1,now.lft=0,now.val=n+1,now.t=0;
	Q.push(now);
	for(int i=1;i<=m;i++)
	{
		int op,x,ans;
		scanf("%d%d",&op,&x);
		if (op==1)
		{
			now=Q.top(),Q.pop();
			while(now.t<tim[now.id]||val[now.id]+1==val[next[now.id]]) now=Q.top(),Q.pop();
			if (now.id==1) ans=1;
			else if (next[now.id]==2) ans=n;
					else ans=now.lft+now.val+1;
			insert(now.id,ans);
			pos[x]=tot;
			printf("%d\n",ans);
		}
		if (op==2) del(pos[x]);
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值