week7-8作业+CSP-M2

41 篇文章 0 订阅
24 篇文章 0 订阅

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} 910i1个,即 9 ∗ l l [ i ] 9*ll[i] 9ll[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] 9ll[i]个,那么所有 i i i位数的位数贡献就是 i ∗ 9 ∗ l l [ i ] i*9*ll[i] i9ll[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[i1]+i,项数为 9 ∗ l l [ i ] 9*ll[i] 9ll[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=kc[i1]calc(b[i1],nll[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;
 } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值