嗯,这次是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
ai≤bi的物品,按照
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
V−ai+bi≥aj
条件2:
V
−
a
j
+
b
j
≥
a
i
V-a_j+b_j\ge a_i
V−aj+bj≥ai
在两个条件中
V
V
V的解集分别为
V
≥
a
i
+
a
j
−
b
i
V\ge a_i+a_j-b_i
V≥ai+aj−bi和
V
≥
a
i
+
a
j
−
b
j
V\ge a_i+a_j-b_j
V≥ai+aj−bj,要使条件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+aj−bj≤ai+aj−bi,解得
b
i
≤
b
j
b_i\le b_j
bi≤bj,由此证得先取
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
L≤i≤R,都有
a
k
∣
a
i
a_k|a_i
ak∣ai,那么我们说这个区间是合法的,价值为
R
−
L
R-L
R−L,求价值最大区间的价值以及所有这些区间的左端点。
做法: 本题需要用到枚举。
对于一个点,我们可以暴力求出以它为公因数的最长合法区间的左端点和右端点,这样做的时间复杂度是
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×jk−∑p=0i∑q=0jCipCjqf(i−p,j−q,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=in∑q=jmCpiCqjf(p−i,q−j,num(k))g(k−1,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=1n∑j=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=1n∑j=1n[gcd(i,j)=1],推一下就可以发现答案为
1
+
2
∑
i
=
2
n
φ
(
i
)
1+2\sum_{i=2}^n\varphi(i)
1+2∑i=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
⌊2j−i⌋−1(这里距离指两辆车之间相隔的车位数),这是一个定值,因此我们用一个链表维护车位被占用的情况,然后再用堆维护距离的最大值即可。时间复杂度
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;
}