2018大连理工大学程序设计竞赛暨蓝桥杯选拔赛题解报告

 

按难度排序大概是:A-F-D-I-G-B-L-C-H-J-K-E

目录

A:签到题1 数一数直接输出就行。

F:签到题2​

D:签到题3​

I:字符串处理​

G:bitset​

B:模拟​

L:逆元+DP​

C:矩阵快速幂​

H:带权并查集+manacher 或者暴力​

J:最短路​

K:拓扑排序​

E:树上差分​


A:签到题1 数一数直接输出就行。

F:签到题2

显然直接乘会产生浮点误差,所以用整形(m*3/10)+((m*3)%10)>4?1:0   来判断得牌数

    	    			
    #include<bits/stdc++.h>
using namespace std;
int n;
int main(){
	int T;
	int a,b;
	scanf("%d%d",&a,&b);
	int tong=b*3/10;
	if((b*3)%10>=5){
		tong++;
	}
	int yin=b*2/10;
	if((b*2)%10>=5){
		yin++;
	}
	int jin=b/10;
	if((b)%10>=5){
		jin++;
	}
//	printf("%d %d %d\n",jin,yin,tong);
	if(a>jin&&a<=jin+yin){
	printf("Sagacious\n");return 0;
	}
	if(a>jin+yin&&a<=jin+yin+tong){
	printf("ShameGhost\n");return 0;
	}
	printf("No Answer\n"); return 0;
}		
    

 

D:签到题3

因为n不超过5所以只用暴力判断2的5次方总情况就行

    	    			
    #include<bits/stdc++.h>
using namespace std;
int n;
int st[20];
int en[20];
char s[300];
int ti[30];
int main(){
	int t;
	scanf("%d",&t);
	for(int i=0;i<t;i++){
		scanf("%s%d%d",s,&st[i],&en[i]);
	}
	int flag=0;
	for(int i=1;i<(1<<t);i++){
		for(int j=0;j<=24;j++){
			ti[j]=0;
		}
		int check=1;int chd=1;
		for(int j=0;j<t;j++){
			if((i>>j)&1){
				for(int k=st[j]+1;k<=en[j];k++){
					if(ti[k]==1){
						check=0;break;
					}
					ti[k]=1;
				}
				if(check==0){
					break;
				}
			}
		}
		if(check==1){
			int nowtime=0;
			for(int i=1;i<=24;i++){
				if(ti[i]==1){
					nowtime++;
				}
			}
			if(nowtime>=20){
				flag=1;break;
			}
		}
	}
	if(flag==0){
		printf("YES\n");
	}
	else{
		printf("NO\n");
	}
}		
    

 

I:字符串处理

因为字符串长度只有100 所以翻转s1后匹配就行 甚至不用kmp

#include<bits/stdc++.h>
using namespace std;
char s1[220];
char s2[220];
int main(){
    gets(s1);
    gets(s2);
    int s1len=strlen(s1);char tchar;
    for(int i=0;i<s1len/2;i++){
        tchar=s1[i];
        s1[i]=s1[s1len-1-i];
        s1[s1len-1-i]=tchar;
    }
    int slen=strlen(s2);
    if(s1len>slen){
        printf("NO\n");
        return 0;
    }
//  printf("%s~\n",s1);
    for(int i=0;i<slen;i++){
        int flag=1;
        for(int j=0;j<s1len;j++){
            if(s2[(i+j)%slen]!=s1[j]){
                flag=0;break;
            }
        }
        if(flag==1){
            printf("YES\n");return 0;
        }
    }
        printf("NO\n");
        return 0;
}

 

G:bitset

题解已经写在提示里了ORZ  开1e8个bitset就可以解决了。

 #include<bits/stdc++.h>
using namespace std;
bitset<100000001>bs;
int a[1000010];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
    }
    int c;
    scanf("%d",&c);
    for(int i=0;i<n;i++){
        if(c<=a[i]) continue;
        if(bs[c-a[i]]) {
        printf("YES\n");return 0;
            }
        bs[a[i]]=1;
    }
    printf("NO\n");return 0;
}

 

B:模拟

就从最后一位开始模拟四舍五入过程,然后舍弃最后的后缀0

#include<bits/stdc++.h>
 
using namespace std;
char s[100010];int en;
void add(int now){
    if(s[now]=='.'){
        add(now-1); return ;
    }
    if(s[now]=='9'){
        s[now]='0';
        add(now-1);
    }
    else{
        s[now]+=1;
    }
}
void cut(){
    while(s[en]=='0'||s[en]=='.'){
        if(s[en]=='.'){
            s[en]='\0';en--; return;
        }
        s[en]='\0';en--;
    }
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%s",s+1);s[0]='0';int point=0;
         
        int slen=strlen(s+1);en=slen;
        for(int i=slen;i>=2;i--){
            if(s[i]=='.') {
                point=1;
            }
            if(s[i]>='5'&&s[i]<='9'){
                if(point){
                    s[i]='0';
                }
                else{
                s[i]='\0';  en=i-1;
                }
                add(i-1);
            }
        }
        cut();
        if(s[0]=='0'){
            printf("%s\n",s+1);
        }
        else{
            printf("%s\n",s);
        }
    }
}

 

L:逆元+DP

Sample Input

4

Sample Output

125000001

题解:如果你知道什么是卡特兰数可以 直接输出。但如果和我一样之前没听过的,可以发现n很小,直接考虑n平方复杂度的DP就行。这题可以替换成一段长度为n的括号序列如“()()(())” 问符合要求的方案数*(2的逆元的n次方)

dp[i][j]表示长度为i,且在右边补上j个右括号就成为符合要求的括号序列方案数。那么递推关系dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];

(如果n很大(1e18) 不知道递推关系的话直接丢进BM板子也是可以出答案的)

#include<bits/stdc++.h>
using namespace std;
const long long mod=1000000007;
long long ans[2010][2010];
long long inv2=(mod-mod/2)%mod;
int main(){
    int n;
    scanf("%d",&n);int sum=0;ans[0][0]=1;
    for(int i=1;i<=n;i++){
        ans[i][0]=ans[i-1][1];
        for(int j=1;j<=n;j++){
            ans[i][j]=ans[i-1][j-1]+ans[i-1][j+1];
            ans[i][j]%=mod;
        }
    }
    for(int i=1;i<=n;i++){
        ans[n][0]*=inv2;
        ans[n][0]%=mod;
    }
    printf("%d\n",ans[n][0]);
}

 

C:矩阵快速幂

递推关系很明显n步到第i号点的方案数=n-1步到第i-1号点的方案数+n-1步到第i+1号点的方案数;

那么把这个丢进矩阵里 然后用矩阵快速幂就可以出答案了复杂度m*m*m*logn

#include<bits/stdc++.h>
using namespace std;
struct ju{
long long ma[101][101]; 
};
const long long mod=1000000007;
ju yuan;
int m;  long long n;
ju cheng(ju a,ju b){
    ju res;
    for(int i=0;i<m;i++){
        for(int j=0;j<m;j++){
            long long now=0;
            for(int k=0;k<m;k++){
                now+=a.ma[i][k]*b.ma[k][j];
                now%=mod;
            }
            res.ma[i][j]=now;
        }
    }
    return res;
}
ju qu(long long n){
    ju res;
    memset(res.ma,0,sizeof(res.ma));
    for(int i=0;i<m;i++){
        res.ma[i][i]=1;
    }
    ju a=yuan;
    while(n){
        if(n&1){
            res=cheng(res,a);
        }
        a=cheng(a,a);
        n>>=1;
    }
    return res;
}
void pp(ju a){
    for(int i=0;i<m;i++){
        for(int j=0;j<m;j++){
            printf("%lld ",a.ma[i][j]);
        }
        printf("\n");
    }
}
int main(){
scanf("%d",&m);
scanf("%lld",&n);
    if(n==0){
        printf("1\n");return 0;
    }
    memset(yuan.ma,0,sizeof(yuan.ma));
    for(int i=0;i<m;i++){
        yuan.ma[((i-1)+m)%m][i]=1;
            yuan.ma[((i+1)+m)%m][i]=1;
    }
     
    ju ans= qu(n);//pp(ans);
    printf("%lld\n",(ans.ma[0][0]+mod)%mod);
}

 

H:带权并查集+manacher 或者暴力

Sample Input

5
1 0 1 1 2 2 2 1 1 0
2
0 b
1 a

Sample Output

baaaa

有一个n平方复杂度的写法,是每个为止 判断每个对称轴会对称到哪一个位置。最后查看所有位置是否相同。

我只有自己写的线性复杂度的代码,不过这种写法比较难写。像manacher一样两字符中间补一个字符。然后对manacher判断的位置进行判断,不过把判断两字符是否相同改为将带权并查集合并。

#include<bits/stdc++.h>
using namespace std;
int a[200040];
int abc[220000];
int abcpos[220000];
int fa[200040];
int ans[200040];
int unans[200020];
int aans[100020];
int find(int i){
//  printf("~~~fa[%d]=%d\n",i,fa[i]);
    if(fa[i]!=i){
        fa[i]=find(fa[i]);
        ans[i]=ans[fa[i]];
        return fa[i];
    }
    return fa[i];
}
int main(){
    int n;
  //   freopen("13.in","r",stdin);
 //   freopen("14.out","w",stdout); 
    scanf("%d",&n);
    for(int i=0;i<2*n;i++){
        scanf("%d",&a[i]);
        a[i]*=2;
    }
    for(int i=0;i<=2*n;i++){
    fa[i]=i;
    }   
    int m;
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int tmp;
        scanf("%d",&tmp);
        char tchar;
        scanf(" %c",&tchar);
        if(aans[tmp]&&aans[tmp]!=tchar){
            printf("-1\n");return 0;
        }  
     
        aans[tmp]=tchar;
        if(abc[tchar]!=0){
        find(2*tmp);
        find(abcpos[tchar]);            
            fa[find(2*tmp)]=find(abcpos[tchar]);
              
        }
        ans[2*tmp]=tchar;
        abc[tchar]++;abcpos[tchar]=tmp*2;
    }
    for(int i=1;i<=n;i++){
        fa[i*2-1]=1;
        ans[i*2-1]=257;
    }
  
    int nowmax=0;
    for(int i=0;i<2*n;i++){
        while(nowmax<=a[i]+i-1){
            if(nowmax<2*n&&(i-nowmax+i)>=0){
                if(find(i-nowmax+i)!=find(nowmax)){
                    if(ans[find(i-nowmax+i)]&&ans[find(nowmax)]&&ans[find(i-nowmax+i)]!=ans[find(nowmax)]){
                    printf("-1\n"); return 0;                       
                    }
                    else{
                if(ans[find(nowmax)]==0) {
                    fa[find(nowmax)]=find(i-nowmax+i);find(nowmax);
                }
                else{
                fa[find(i-nowmax+i)]=find(nowmax);  
                find(i-nowmax+i);
                }                       
                    }
                }
            }
            nowmax++;
        }
    }
/*  for(int i=0;i<2*n;i++){
        find(i);
    //  printf("%d %d %d\n",i,fa[i],ans[i]);
    }*/
    for(int i=0;i<2*n;i++){
        if(i+a[i]<2*n&&i-a[i]>=0&&(i-a[i])%2==0){
            if(find(i-a[i])==find(i+a[i])){
                    printf("-1\n"); return 0;
            }
        }
        if(i+a[i]+1<2*n&&i-a[i]-1>=0&&(i-a[i])%2!=0){
            if(find(i-a[i]-1)==find(i+a[i]+1)){
                    printf("-1\n"); return 0;
            }
        }
    }
    int unknow=0;
    for(int i=0;i<n;i++){
        if(ans[find(2*i)]==0){
            unans[unknow]=i;
            unknow++;
        }
    }
    if(unknow){
        for(int i=0;i<unknow;i++){
            if(i!=0) printf(" ");
            printf("%d",unans[i]);
        }
        printf("\n");
    }
    else{
        for(int i=0;i<n;i++){
            printf("%c",ans[find(2*i)]);
        }
        printf("\n");
    }
}

 

J:最短路

公交路线上每个点建一个离原点inf(一个比途径车站数多的值  比如20000)权值的边 然后建离同一路线下一车站1权值的边..然后跑最短路

则换线数则(最短路权值/40000)-1经过车站数(最短路权值%40000)

换线相当于先走20000的路径到那个点再走20000路径到需要换到的路径的车站点。

#include<bits/stdc++.h>
using namespace std; 

const int INF=0x3f3f3f3f;
const int MAXN=400010;
struct qnode
{
    int v;
    int c;
    qnode(int _v=0,int _c=0):v(_v),c(_c){}
    bool operator <(const qnode &r)const
    {
        return c>r.c;
    }
};
struct Edge
{
    int v,cost;
    Edge(int _v=0,int _cost=0):v(_v),cost(_cost){}
};
vector<Edge>E[MAXN];
bool vis[MAXN];
int dist[MAXN];
void Dijkstra(int n,int start)//点的编号从1开始
{
    memset(vis,false,sizeof(vis));
    for(int i=1;i<=n;i++)dist[i]=INF;
    priority_queue<qnode>que;
    while(!que.empty())que.pop();
    dist[start]=0;
    que.push(qnode(start,0));
    qnode tmp;
    while(!que.empty())
    {
        tmp=que.top(); que.pop();
        int u=tmp.v;
        if(vis[u])continue;
        vis[u]=true;
        for(int i=0;i<E[u].size();i++)
        {
            int v=E[tmp.v][i].v;
            int cost=E[u][i].cost;
            if(!vis[v]&&dist[v]>dist[u]+cost)
            {
                dist[v]=dist[u]+cost; que.push(qnode(v,dist[v]));
            }
        }
    }
}
void addedge(int u,int v,int w)
{
    E[u].push_back(Edge(v,w));
}
int main()
{
    int n,m;
    cin>>n>>m;
    int st,en;
	scanf("%d%d",&st,&en); 
    for(int i=0;i<m;i++)
    {
    	int mi;
    	scanf("%d",&mi);
        int now;scanf("%d",&now);
        n++; addedge(n,now,20000);addedge(now,n,20000);
        for(int j=1;j<mi;j++){
		scanf("%d",&now);
        n++; addedge(n,now,20000);addedge(now,n,20000);
        addedge(n-1,n,1);addedge(n,n-1,1);
		}
    }
    
    if(st==en){
    	printf("0 0\n"); return 0;
	}
    Dijkstra(n,st);
   if(dist[en]==INF){
   	printf("-1\n");
   }
   else{
   	printf("%d %d\n",(dist[en]/40000)-1,dist[en]%40000);
   }
}

 

K:拓扑排序

显然拓扑排序在A之后的点都能为A点增加一个SG值,按拓扑排序依次加入A的后宫即可√

 

#include<bits/stdc++.h>
using namespace std;
int n,m;int A;int shang;
int du[1010];
int ma[1010][1010];int bian=0;
int ans[1010];int ansnow=0;
void dfs(int now){
	if(now==A) return ;
			ansnow++;ans[ansnow]=now;
			for(int i=1;i<ansnow;i++){
				if(ma[now][ans[i]]==0){
				ma[now][ans[i]]=2;
				bian++;			
				}
			}	
	for(int i=1;i<=n;i++){
		if(ma[i][now]==1){
			du[i]--;
			if(du[i]==0){
				dfs(i); 
			} 
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;i++){
		int ta,tb;
		scanf("%d%d\n",&ta,&tb);
		ma[ta][tb]=1;
		du[ta]++;
	}
	scanf("%d",&A);shang=n;bian=0;
	for(int i=1;i<=n;i++){
		if(du[i]==0){
			dfs(i);
		}
	}
			for(int i=1;i<=ansnow;i++){
				if(ma[A][ans[i]]==0){
				ma[A][ans[i]]=2;
				bian++;			
				}
			}
	printf("%d\n",ansnow);
			printf("%d\n",bian);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
		if(ma[i][j]==2){
			printf("%d %d\n",i,j);
		}
		}
	}
}		
    		
    

 

E:树上差分

吐槽:第一眼看到...稍微一改不是用徐州G的写法就能过么,但是那个要150行就没敢上orz代码能力菜出天际,假期好好练才行。至今不知道潼神60行的写法。(代码是直接拿我上次补的徐州G题改的)

首先生成树只破坏一条边,则剩下两部分必定联通,那么就变成求破坏生成树上任意一条边后分成的两个联通块的相连边的权值和。在生成树中定一个点(例如1)为顶点,那么假如有一条非生成树边 a b 权值为w。那么只有 当前破坏的生成树的边 在生成树的a到b这条路径 才需要破坏掉这个非生成树边。那么就变成生成树上a到b的路径加上权值W。然后就可以树上差分解决了。

#include <bits/stdc++.h>
#define MAXN 101001
#define MAXM 101001
#pragma comment(linker, "/STACK:1024000000,1024000000") 
using namespace std;
struct Edge
{
    int from, to, next;
};
Edge edge[MAXM];
int numd;
int head[MAXN], edgenum;
int k;
int vs[MAXN<<1];//第i次DFS访问节点的编号
int depth[MAXN<<1];//第i次DFS访问节点的深度
int id[MAXN];//id[i] 记录在vs数组里面 i节点第一次出现的下标
int dfs_clock;//时间戳
int N, M, Q;//点数 边数 查询数
int dp[MAXN<<1][21];//dp[i][j]存储depth数组  以下标i开始的,长度为2^j的区间里 最小值所对应的下标
long long ans;
long long edgw[200050];
void init()
{
    edgenum = 0;
    memset(head, -1, sizeof(head));
}
void addEdge(int u, int v,long long w)
{
    Edge E = {u, v, head[u]};
    edge[edgenum] = E;
    edgw[edgenum]=w;
    head[u] = edgenum++;
}
int DFSd;
int fa[200200];
int u[200200];
inline void DFS()//当前遍历点以及它的父节点  遍历点深度
{
    id[u[DFSd]] = dfs_clock;
    vs[dfs_clock] = u[DFSd];
    depth[dfs_clock++] = DFSd;
    for(int i = head[u[DFSd]]; i != -1; i = edge[i].next)
    {
        if(edge[i].to == fa[DFSd]) continue;
        DFSd++;
        fa[DFSd]=u[DFSd-1];u[DFSd]=edge[i].to;
        DFS();
        DFSd--;
        vs[dfs_clock] = u[DFSd];//类似 回溯
        depth[dfs_clock++] = DFSd;
    }
}
void find_depth()
{
    dfs_clock = 1;
    memset(vs, 0, sizeof(vs));
    memset(id, 0, sizeof(id));
    memset(depth, 0, sizeof(depth));
    DFSd=0;
    fa[0]=-1,u[0]=1;
    DFS();//遍历
}
void RMQ_init(int NN)//预处理 区间最小值
{
    for(int i = 1; i <= NN; i++)
        dp[i][0] = i;
    for(int j = 1; (1<<j) <= NN; j++)
    {
        for(int i = 1; i + (1<<j) - 1 <= NN; i++)
        {
            int a = dp[i][j-1];
            int b = dp[i + (1<<(j-1))][j-1];
            if(depth[a] <= depth[b])
                dp[i][j] = a;
            else
                dp[i][j] = b;
        }
    }
}
int query(int L, int R)
{
    //查询L <= i <= R 里面使得depth[i]最小的值 返回对应下标
    int k = 0;
    while((1<<(k+1)) <= R-L+1) k++;
    int a = dp[L][k];
    int b = dp[R - (1<<k) + 1][k];
    if(depth[a] <= depth[b])
        return a;
    else
        return b;
}
int LCA(int u, int v)
{
    int x = id[u];//比较大小 小的当作左区间 大的当作右区间
    int y = id[v];
    if(x > y)
        return vs[query(y, x)];
    else
        return vs[query(x, y)];
}
long long pl[202020];

long long dfs(int u, int fa,long long lastw)//当前遍历点以及它的父节点  遍历点深度
{
	long long sum=0;sum+=pl[u];
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        if(edge[i].to == fa) continue;
       sum+=dfs(edge[i].to, u,edgw[i]);
    }
	if(fa!=-1){
		ans=min(ans,sum+lastw);
	}
    return sum;
}
int main()
{
	int t;int a,b;long long w;
		scanf("%d%d", &N, &M);ans=0x3f3f3f3f3f3f3f3f;	
        init();
        if(N-1>M){
        	printf("0\n");return 0;
		}
        if(N==1){
        	printf("0\n");return 0;
		}
        for(int i=0;i<N-1;i++)
        {
        scanf("%d%d", &a, &b),scanf("%lld",&w);
        addEdge(a, b, w), addEdge(b, a, w);
		}
        find_depth();//DFS遍历整个树 求出所需要的信息
        RMQ_init(dfs_clock - 1);
        for(int i=N-1;i<M;i++){
        	scanf("%d%d",&a,&b);scanf("%lld",&w);
			int tlca=LCA(a,b);
			if(tlca==a){
        		pl[a]-=w;
        		pl[b]+=w;continue;
			}
			if(tlca==b){
        		pl[b]-=w;
        		pl[a]+=w;continue;
			}
			else{
        		pl[tlca]-=2*w;				
        		pl[a]+=w;
        		pl[b]+=w;continue;
			}
		}
		numd=0;
		dfs(1,-1,0);
		printf("%lld\n",ans);		
    
    return 0;
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值