week7作业
B-TT的旅行日记
题目大意
给出起点终点、每条经济线和商务线的起始车站以及单程花费的时间(双向边),求从起点到终点所花费的最小时间。
特别多,最多只能选择一条商务线。
题解
最短路
用所有的经济线建图
以起点为源点求单源最短路,得到
d
i
s
1
dis1
dis1
以终点为源点求单源最短路,得到
d
i
s
2
dis2
dis2
枚举每条商业线
(
u
,
v
,
t
)
(u,v,t)
(u,v,t),求
m
i
n
(
d
i
s
1
[
u
]
+
d
i
s
2
[
v
]
+
t
,
d
i
s
[
v
]
+
d
i
s
2
[
u
]
+
t
)
min(dis1[u]+dis2[v]+t,dis[v]+dis2[u]+t)
min(dis1[u]+dis2[v]+t,dis[v]+dis2[u]+t)
最后在与不选取商业线的答案取
m
i
n
min
min即可
特别的,要注意输出格式,不要有多余的换行和空格。
#include<cstdio>
#include<cstring>
#include<queue>
#define N 100003
#define M 503
#define pa pair<int,int>
using namespace std;
const int inf=1e9;
int tot,n,m,s,e,point[M],nxt[N],v[N],l[N];
int dis1[M],dis2[M],pre1[M],pre2[M];
void clear()
{
for (int i=1;i<=n;i++) dis1[i]=dis2[i]=inf;
tot=0;
memset(point,0,sizeof(point));
memset(pre1,0,sizeof(pre1));
memset(pre2,0,sizeof(pre2));
}
void add(int x,int y,int z)
{
tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; l[tot]=z;
}
void dijkstra(int *dis,int *pre,int k)
{
priority_queue<pa,vector<pa>,greater<pa> > p;
dis[k]=0; p.push(make_pair(dis[k],k));
while (!p.empty()){
int now=p.top().second; p.pop();
for (int i=point[now];i;i=nxt[i])
if (dis[v[i]]>dis[now]+l[i]){
dis[v[i]]=dis[now]+l[i];
pre[v[i]]=now;
p.push(make_pair(dis[v[i]],v[i]));
}
}
}
void print(int x)
{
if(x==s) {
printf("%d",s);
return;
}
print(pre1[x]);
printf(" %d",x);
}
void print1(int x)
{
if (x==e) {
printf("%d\n",e);
return;
}
printf("%d ",x);
print1(pre2[x]);
}
int main()
{
int opt=0;
while(scanf("%d%d%d",&n,&s,&e)!=EOF) {
if (opt) printf("\n");
opt++;
clear();
scanf("%d",&m);
for(int i=1;i<=m;i++) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
dijkstra(dis1,pre1,s);
dijkstra(dis2,pre2,e);
int ans=inf; int u,v;
scanf("%d",&m);
for(int i=1;i<=m;i++) {
int x,y,z; scanf("%d%d%d",&x,&y,&z);
if (dis1[x]+dis2[y]+z<ans) {
ans=dis1[x]+dis2[y]+z;
u=x;
v=y;
}
if (dis1[y]+dis2[x]+z<ans) {
ans=dis1[y]+dis2[x]+z;
u=y;
v=x;
}
}
if (ans<dis1[e]) {
print(u); printf(" ");
print1(v);
printf("%d\n",u);
printf("%d\n",ans);
}
else {
print(e);
printf("\n");
printf("Ticket Not Used\n");
printf("%d\n",dis1[e]);
}
}
}
week8作业
C-班长竞选
题目大意
关系具有传递性,即
A
−
>
B
,
B
−
>
C
A->B,B->C
A−>B,B−>C,可以得到
A
−
>
C
A->C
A−>C
设能到达当前点x的点数为
a
n
s
[
x
]
ans[x]
ans[x](不包含自己),求
m
a
x
(
a
n
s
[
x
]
)
max(ans[x])
max(ans[x])和x(输出所有可能的x)
题解
tarjan求强连通分量
tarjan缩点后对图进行重建,重建后的节点i,表示的强连通块i,然后对这个节点规模明显减小的图的每个节点做dfs,如果i能到达x,则
a
n
s
[
x
]
+
=
s
i
z
e
[
i
]
ans[x]+=size[i]
ans[x]+=size[i],
a
n
s
[
x
]
ans[x]
ans[x]中记入
s
i
z
e
[
x
]
size[x]
size[x],其中
s
i
z
e
size
size表示连通块的大小。
那么对于连通块中所有的点,答案就是
a
n
s
[
x
]
−
1
ans[x]-1
ans[x]−1(减去自己)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 30003
using namespace std;
int n,m,tot,top,sz,cnt;
int point[N],ins[N],dfsn[N],low[N],nxt[N],v[N],belong[N],size[N],st[N];
int xx[N],yy[N],ans[N];
void clear()
{
tot=0; top=0; sz=0; cnt=0;
memset(point,0,sizeof(point));
memset(ins,0,sizeof(ins));
memset(dfsn,0,sizeof(dfsn));
memset(low,0,sizeof(low));
memset(ans,0,sizeof(ans));
memset(size,0,sizeof(size));
}
void add(int x,int y)
{
tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
}
void tarjan(int x)
{
st[++top]=x; ins[x]=1;
dfsn[x]=low[x]=++sz;
for (int i=point[x];i;i=nxt[i])
{
int j=v[i];
if (!dfsn[j]) {
tarjan(j);
low[x]=min(low[x],low[j]);
}
else if (ins[j]) low[x]=min(dfsn[j],low[x]);
}
int j;
if (low[x]==dfsn[x]) {
cnt++;
do{
j=st[top--];
ins[j]=0;
belong[j]=cnt;
size[cnt]++;
}while (j!=x);
}
}
void dfs(int fa,int x)
{
ans[x]+=size[fa]; ins[x]=1;
for (int i=point[x];i;i=nxt[i])
if (!ins[v[i]]) dfs(fa,v[i]);
}
int main()
{
int T;
scanf("%d",&T);
for (int t=1;t<=T;t++) {
scanf("%d%d",&n,&m);
clear();
for (int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
xx[i]=x; yy[i]=y;
}
for (int i=0;i<n;i++)
if (!dfsn[i]) tarjan(i);
tot=0;
memset(point,0,sizeof(point));
memset(ins,0,sizeof(ins));
for (int i=1;i<=m;i++)
if (belong[xx[i]]!=belong[yy[i]]) {
add(belong[xx[i]],belong[yy[i]]);
}
for (int i=1;i<=cnt;i++) {
for (int j=1;j<=cnt;j++) ins[j]=0;
dfs(i,i);
}
int mx=0;
for (int i=1;i<=cnt;i++) mx=max(mx,ans[i]);
printf("Case %d: %d\n",t,mx-1);
bool pd=false;
for(int i=0;i<n;i++)
if (ans[belong[i]]==mx) {
if (pd) printf(" %d",i);
else printf("%d",i);
pd=true;
}
printf("\n");
}
return 0;
}
CSP-M2
C-咕咕东的奇妙序列
题目大意
求数列
112123123412345.......
112123123412345.......
112123123412345.......的第k位数字
k
m
a
x
=
1
e
18
k_{max}=1e18
kmax=1e18
题解
二分+前缀和预处理
直接对原始序列
112123123412345.....
112123123412345.....
112123123412345.....,求解不是很容易,所以我们把数列拆开来看
1
,
12
,
123
,
1234
,
.
.
.
.
.
.
1,12,123,1234,......
1,12,123,1234,......,我们首先要找到第
k
k
k为数字在第几个数列中,然后问题就转换成了在
12345......
n
12345......n
12345......n中求某一位的问题。
要是
k
k
k达到
1
e
18
1e18
1e18,
n
n
n需要达到
1
e
10
1e10
1e10,这个范围我们无法直接预处理这
1
e
10
1e10
1e10个序列,因而无法通过二分一下子找到
n
n
n,所以还是需要进行进一步的优化。
无法直接找到n,那么我们先考虑如何去确定
n
n
n的位数。
l
l
[
i
]
ll[i]
ll[i],
r
r
[
i
]
rr[i]
rr[i]用来记录i位数的左右端点,例如
l
l
[
1
]
=
1
,
r
r
[
1
]
=
9
,
l
l
[
2
]
=
10
,
r
r
[
2
]
=
99
ll[1]=1,rr[1]=9,ll[2]=10,rr[2]=99
ll[1]=1,rr[1]=9,ll[2]=10,rr[2]=99,那不难发现i位数有
9
∗
1
0
i
−
1
9*10^{i-1}
9∗10i−1个,即
9
∗
l
l
[
i
]
9*ll[i]
9∗ll[i]个。
b
[
i
]
b[i]
b[i]表示
123....
r
r
[
i
]
123....rr[i]
123....rr[i]总共的位数,这个东西其实是个前缀和,i位数有
9
∗
l
l
[
i
]
9*ll[i]
9∗ll[i]个,那么所有
i
i
i位数的位数贡献就是
i
∗
9
∗
l
l
[
i
]
i*9*ll[i]
i∗9∗ll[i]
然后利用
b
[
i
]
b[i]
b[i]我们可以去预处理
c
[
i
]
c[i]
c[i],
c
[
i
]
c[i]
c[i]表示
1
,
12
,
123
,
.
.
.
.
.
,
12...
r
r
[
i
]
1,12,123,.....,12...rr[i]
1,12,123,.....,12...rr[i]这所有数列的位数和,c[i]也可通过前缀和来求。对于
i
i
i位数,他所对应的所有数列的长度实际上是一个等差数列,公差为
i
i
i,首项是
b
[
i
−
1
]
+
i
b[i-1]+i
b[i−1]+i,项数为
9
∗
l
l
[
i
]
9*ll[i]
9∗ll[i],所以我们可以用等比数列求和公式来求位数i这一部分所有数列的和。
预处理完毕后,第一个大于等于
k
k
k的
c
[
i
]
c[i]
c[i]所对应的的
i
i
i就是
n
n
n的位数,上面提到了对于位数相同的数,即
[
l
l
[
i
]
,
r
r
[
i
]
]
[ll[i],rr[i]]
[ll[i],rr[i]]区间的数,他们
123....
n
123....n
123....n的数列位数是满足等差数列的,所以我们在确定了位数之后,就可以通过二分找到我们想要的n的确定值(利用等差数列)。
然后问题就变成了求
1234....
n
1234....n
1234....n中第
x
=
k
−
c
[
i
−
1
]
−
c
a
l
c
(
b
[
i
−
1
]
,
n
−
l
l
[
i
]
,
i
)
x=k-c[i-1]-calc(b[i-1],n-ll[i],i)
x=k−c[i−1]−calc(b[i−1],n−ll[i],i)个数字,
c
a
l
c
calc
calc是等差数列求和。求这个问题的时候我们可以借助
b
b
b数组先确定x所在的数的位数,然后找到他所在的数和所在这个数的第几位。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 1000003
#define LL long long
using namespace std;
LL a[20],b[20],c[20],ll[20],rr[20];
LL calc(LL x,LL num,LL pos)
{
return (x+pos+x+num*pos)*num/2;
}
void init()
{
LL l=1,r;
for (LL i=1;i<=10;i++) {
r=l*10-1;
ll[i]=l; rr[i]=r;
b[i]=b[i-1]+(LL)9*l*i;
c[i]=c[i-1]+calc(b[i-1],(LL)9*l,i);
l*=10;
}
}
int find_N(LL n)
{
LL i=1;
for (;b[i]<n;i++);
LL aa=n-b[i-1];
LL num=ll[i]+(aa-1)/i;
LL k=(aa-1)%i;
string s=to_string(num);
return s[k]-'0';
}
int main()
{
int q; LL ki;
init();
scanf("%d",&q);
while (q--) {
scanf("%lld",&ki);
int pos=lower_bound(c+1,c+11,ki)-c;
ki-=c[pos-1];
LL l=ll[pos],r=rr[pos],ans=rr[pos]+1;
while(l<=r) {
LL mid=(l+r)/2;
if (calc(b[pos-1],mid-ll[pos]+1,pos)>=ki) {
r=mid-1;
ans=min(ans,mid);
}
else l=mid+1;
}
ki-=calc(b[pos-1],ans-ll[pos],pos);
printf("%d\n",find_N(ki));
}
return 0;
}