钉耙编程(2)

目录

1001  鸡爪

Problem Description

解析:

代码:

1002  梦中的地牢战斗

Problem Description

解析:

标准答案代码:

1006 传奇勇士小凯

Problem Description

题解

标准答案代码:

1011  在 A 里面找有 C 的 B

Problem Description

解析:

代码:(KMP,AC自动机 抄的模板)


1001  鸡爪

Problem Description

一个鸡爪是由 44 个部分组成,一个点与三个与该点相邻的边,三个边的另一端点被认为不在鸡爪中。

一个图上的鸡爪数是该图最多成形成几个鸡爪,使得图上每个点与边最多一个鸡爪中(注意上文点与边是否在鸡爪中的定义)。

现在给你 𝑛n 条边,你可以使用任意个点,构造一个简单无向图(没有自环重边),要求最大化该图的鸡爪数,并输出𝑛n条边的两端点。如果有多解,请让输出的 2𝑛2n 个数字在行优先遍历的顺序下,字典序最小

字典序:序列𝐴A的字典序小于序列𝐵B,当且仅当存在𝑖i (1≤𝑖≤𝑛1≤i≤n),使得𝐴[𝑖]<𝐵[𝑖]A[i]<B[i],且对任意的𝑗j (1≤𝑗<𝑖1≤j<i),𝐴[𝑗]=𝐵[𝑗]A[j]=B[j]。

Input

第一行为一个整数 𝑇T(1≤𝑇≤104)(1≤T≤104) ,表示测试样例个数。

每个样例一行,为一个整数 𝑛n(1≤𝑛≤1041≤n≤104)。保证所有样例的𝑛n的和≤104≤104。

Output

每个样例输出 𝑛n 行,每行两个正整数,表示该无向边连接的两个顶点(顶点从 11 开始编号)

解析:

因为鸡爪需要连3个不属于鸡爪的点,所以[4,n/3]中心点应该都连(1,2,3)各构成一个鸡爪。

(n/3+1,n/3+2,n/3+3)是为了构成以1,2,3)为中心点的鸡爪而增加的辅助点。(边不够,首先应该要3先选边,再2,再1,不够要增加时,后选边的点增边时,序列数更小)

代码:

#include<iostream>
#include<cstdio>
using namespace std;
int main(){
	freopen("D:\\1001(2).txt", "w", stdout);
	int t; cin>>t;
	while(t--){
		int n; scanf("%d",&n);
		int pt=n/3+3, ptt=n%3;
		if(pt==3){
			for(int i=2;i<ptt+2;i++)
			   printf("1 %d\n",i);
		}
		else if(pt>=5){
			for(int i=2;i<=pt+ptt;i++)
			   printf("1 %d\n",i);
			pt--;
			for(int i=3;i<=pt;i++)
			   printf("2 %d\n",i);
			pt--;
			for(int i=4;i<=pt;i++)
			   printf("3 %d\n",i);
		}
		else{
			for(int i=2;i<=pt+ptt;i++)
			   printf("1 %d\n",i);
		}
	}
	return 0;
}

1002  梦中的地牢战斗

Problem Description

众所周知,小凯是一个网瘾少年,这天梦里,他又梦到了自己在玩一款游戏——

在一张 𝑛×𝑚n×m 大小的地牢里(左下角为 (1,1)(1,1) ,右上角为 (𝑛,𝑚)(n,m) ),有 𝐾K 个怪物,小凯操控着主角为了获取收益来进入地牢猎杀这些怪物。

对于每个怪物有这样三个属性,价值 𝐴𝑖Ai​ ,攻击力 𝐵𝑖Bi​ ,攻击距离 𝐶𝑖Ci​ 。

主角的初始生命值为1主角的初始生命值为1,在进入地牢前,你会有一个商店供你购买生命值,你可以贷款 𝑥x 个金币( 𝑥x 为任意正整数)来获得 𝑥x 点生命值,当然你也可以选择不贷款。注意你只能在进入地牢前购买生命值,进入地牢后将无法购买生命值。

一开始,主角会出现在地图的 (𝑠𝑥,𝑠𝑦)(sx,sy) 位置,保证该位置上没有怪物。

在每回合的开始,主角可以进行下面两个操作中的一个操作。

1.离开地牢,获得当前击杀怪物的金币并进行结算。

2.瞬移到同行/同列同行/同列上与当前位置距离不超过 𝑑d 的没有怪物的地图内的位置没有怪物的地图内的位置,并且消灭瞬移起点和终点相连的线段上的所有怪物。(消灭怪物可以瞬间获得怪物的价值数量的金币)这里的距离可以用曼哈顿距离来理解。

在每回合结束时,会结算怪物对主角造成的伤害。(死亡的怪物不会对主角造成伤害)

对于每个怪物而言,如果主角和怪物之间的曼哈顿距离小于等于小于等于怪物的攻击距离 𝐶𝑖Ci​ ,那么主角就会受到 𝐵𝑖Bi​ 点伤害。在任意时刻主角的生命值小于等于0小于等于0角色就会死亡,并且失去所有获得的金币失去所有获得的金币。

试问主角最多能获得多少金币。(最后收益为击杀怪物获得金币减去一开始贷款购买的生命值的花费)

曼哈顿距离的定义如下,对于点 𝑆(𝑥1,𝑦1)S(x1​,y1​) 和点 𝑇(𝑥2,𝑦2)T(x2​,y2​) ,他们的曼哈顿距离为 ∣𝑥1−𝑥2∣+∣𝑦1−𝑦2∣∣x1​−x2​∣+∣y1​−y2​∣

Input

第一行有一个整数,𝑇T (1≤𝑇≤151≤T≤15) ,代表数组组数

每组数据第一行有三个整数, 𝑛,𝑚,𝐾n,m,K (2≤𝑛,𝑚≤302≤n,m≤30,1≤𝐾≤101≤K≤10)

第二行是一个整数, 𝑑d (1≤𝑑≤81≤d≤8),代表瞬移的距离上限

接下来有𝐾K行,每行有五个整数 𝑋𝑖,𝑌𝑖,𝐴𝑖,𝐵𝑖,𝐶𝑖Xi​,Yi​,Ai​,Bi​,Ci​ 。其中 𝑋𝑖,𝑌𝑖Xi​,Yi​ 表示怪物现在所在点(𝑋𝑖,𝑌𝑖)(Xi​,Yi​) , 𝐴𝑖,𝐵𝑖,𝐶𝑖Ai​,Bi​,Ci​分别表示怪物𝑖i的价值、攻击力和攻击距离。(1≤𝑋𝑖≤𝑛,1≤𝑌𝑖≤𝑚,1≤𝐴𝑖,𝐵𝑖≤104,1≤𝐶𝑖≤81≤Xi​≤n,1≤Yi​≤m,1≤Ai​,Bi​≤104,1≤Ci​≤8)

最后一行两个整数 𝑠𝑥,𝑠𝑦sx,sy 表示主角一开始在的位置。(1≤𝑠𝑥≤𝑛,1≤𝑠𝑦≤𝑚1≤sx≤n,1≤sy≤m)​

Output

对于每组数据输出一行一个整数代表最多可以获得的收益。

解析:

标准答案代码:

#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
#define M 55
using namespace std;
struct data{
    int x,y,val,d,len;  //价值、攻击力和攻击距离
}A[M];
int mp[M][M];   //存储每个点的怪兽编号(从1开始),没有怪兽则为0; 
int dp[M][M][1<<10];   //当主角在(i,j),怪兽在状态chs时,最多能获得的金币。 
int cost[M][M][1<<10]; //当主角在(i,j),怪兽在状态chs时,受到的伤害。 
int n,m,K,lim,I;
//计算cost 
void Init(){
    memset(cost,0,sizeof(cost));
    for(int chs=0;chs<=I;chs++){  //怪兽的每个状态 
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                for(int k=1;k<=K;k++){  //枚举每个怪兽 
                    if((1<<(k-1))&chs)continue;  //第K只怪兽在该状态下已经死了 
                    int d=abs(i-A[k].x)+abs(j-A[k].y); //(i,j)与怪兽的曼哈顿距离 
                    if(d<=A[k].len)cost[i][j][chs]+=A[k].d;
                }
            }
        }
    }
}
bool done[M][M];  //该点有没有访问过 
struct node{
    int x,y,val;
    bool operator <(const node &_)const{
        return val<_.val;  //降序,从大到小 
    }
};
int ans;
int xr[]={0,-1,0,1};
int yr[]={1,0,-1,0}; //上下左右 
bool check(int x,int y){
    return 1<=x&&x<=n&&1<=y&&y<=m;  //是否在没有超出地图 
}
void check_max(int &x,int y){
    if(x<y)x=y;             //x=max(x,y);
}
void Solve(int chs){
    memset(done,0,sizeof(done));
    priority_queue<node>Q;
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
        if(dp[i][j][chs]>-1e8){
            Q.push((node){i,j,dp[i][j][chs]});
        }
    }
    while(!Q.empty()){
        node now=Q.top();Q.pop();
        if(done[now.x][now.y])continue;
        done[now.x][now.y]=true;
        ans=max(ans,dp[now.x][now.y][chs]); //在所以状态中选择最大的,不是要把所以的怪兽消灭完 
        for(int op=0;op<4;op++){  //向上下左右移动 
            int pass=0,tot_val=0;
            for(int i=1;i<=lim;i++){ //枚举距离 
                node nxt=now;
                nxt.x+=xr[op]*i;
                nxt.y+=yr[op]*i;
                if(!check(nxt.x,nxt.y))break; //超出距离break; 
                if(mp[nxt.x][nxt.y]!=0&&!((1<<(mp[nxt.x][nxt.y]-1))&chs)){ //有怪兽且怪兽在该状态是活的,消灭怪兽 
                    pass|=1<<(mp[nxt.x][nxt.y]-1);     //记录怪兽编号 
                    tot_val+=A[mp[nxt.x][nxt.y]].val;  //获取怪兽val 
                }else if(!pass){    //一路走来没有消灭怪兽,该点没有怪兽活着 ,状态chs没有改变要push q 
                    int nxt_dp=dp[now.x][now.y][chs]-cost[nxt.x][nxt.y][chs];
                    if(dp[nxt.x][nxt.y][chs]<nxt_dp){
                        dp[nxt.x][nxt.y][chs]=nxt_dp;
                        Q.push((node){nxt.x,nxt.y,nxt_dp});
                    }
                }else {  //路上消灭了怪兽,抵达了新的状态,该点没有怪兽活着,由chs转来,在chs中死了的怪兽,在新状态不可能复活 
                    check_max(dp[nxt.x][nxt.y][chs|pass],dp[now.x][now.y][chs]+tot_val-cost[nxt.x][nxt.y][chs|pass]); //更新新状态的最大金币 
                }
            }
        }
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        ans=0;
        for(int i=0;i<M;i++)for(int j=0;j<M;j++)for(int k=0;k<(1<<10);k++)dp[i][j][k]=-1e9;
        memset(mp,0,sizeof(mp));
        scanf("%d%d%d",&n,&m,&K);
        scanf("%d",&lim);
        for(int i=1;i<=K;i++){
            scanf("%d%d%d%d%d",&A[i].x,&A[i].y,&A[i].val,&A[i].d,&A[i].len);
            mp[A[i].x][A[i].y]=i;
        }
        int sx,sy;
        scanf("%d%d",&sx,&sy);
        dp[sx][sy][0]=0;  //0:活, 1:死 
        I=1<<K;I--;  //状态的最大值。 
        Init(); //计算cost数组 
        for(int chs=0;chs<=I;chs++){
            Solve(chs);
        }
        printf("%d\n",ans);
    }
    return 0;
}

1006 传奇勇士小凯

Problem Description

略,见简要题意;

题解

概率论相关知识:

 

求最大公约数和最小公倍数:

标准答案代码:

#include<cstdio>
#include<algorithm>
#define M 100005
using namespace std;
struct E{
	int to,nx;
}edge[M<<1];
int tot,head[M];
void Addedge(int a,int b){
	edge[++tot].to=b;
	edge[tot].nx=head[a];
	head[a]=tot;
}
int A[M];
int fa[M];
long long gcd(long long a,long long b){
	if(!b)return a;
	return gcd(b,a%b);
}
long long lcm(long long a,long long b){
	return a/gcd(a,b)*b;
}
struct node{
	long long a,b;
	bool operator <(const node &_)const{
		long long tmp=lcm(b,_.b);
		return a*(tmp/b)<_.a*(tmp/_.b);  
	}
	node operator +(const node &_)const{
		node res;
		res.b=lcm(b,_.b);
		res.a=a*(res.b/b)+_.a*(res.b/_.b);
		if(res.a==0)return (node){0,1};
		long long g=gcd(res.a,res.b);
		res.a/=g;
		res.b/=g;
		return res;
	}
}dp[M];
void dfs(int now){
//	printf("now=%d\n",now);
	dp[now]=(node){0,1};
	for(int i=head[now];i;i=edge[i].nx){
		int nxt=edge[i].to;
		if(nxt==fa[now])continue;
		fa[nxt]=now;
		dfs(nxt);
		dp[now]=max(dp[now],dp[nxt]);  
	}
	dp[now]=dp[now]+(node){15,A[now]};
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		int n;
		scanf("%d",&n);
		tot=0;for(int i=1;i<=n;i++)head[i]=0;
		for(int i=1;i<n;i++){
			int a,b;
			scanf("%d%d",&a,&b);
			Addedge(a,b);
			Addedge(b,a);
		}
		for(int i=1;i<=n;i++)scanf("%d",&A[i]);
		dfs(1);
		printf("%lld/%lld\n",dp[1].a,dp[1].b);
	}
	return 0;
}

1011  在 A 里面找有 C 的 B

Problem Description

略,从input和output可知。

Input

第一行输入一个正整数 𝑇T (1≤𝑇≤101≤T≤10),表示总共有 𝑇T 组数据。

对于每一组测试数据,首先是一个正整数 𝑛n (1≤𝑛≤1051≤n≤105)。

接下来是两个由小写字母构成的字符串 𝐴A,𝐶C (1≤∣𝐴∣≤1051≤∣A∣≤105,1≤∣𝐶∣≤1041≤∣C∣≤104)

接下来 𝑛n 行,每行读入两个由小写字母构成的字符串 𝐵𝑖Bi​,𝐵𝑖′Bi′​ (1≤∣𝐵𝑖∣≤104,1≤∣𝐵𝑖′∣≤1051≤∣Bi​∣≤104,1≤∣Bi′​∣≤105)

每组数据中有额外限制如下:∑∣𝐵𝑖∣≤105,∑∣𝐵𝑖′∣≤5×105∑∣Bi​∣≤105,∑∣Bi′​∣≤5×105

Output

对于每一组测试数据,输出一行整数 𝑖i 满足 𝐵𝑖Bi​ 在 𝐴A 中,同时 𝐵𝑖′Bi′​ 中包含 𝐶C,相邻的整数之间用空格分割,注意行末不保留空格

假如答案集合为空,则输出一个空行。

解析:

在多个s中找一个p:KMP/hash;

在一个s中找多个p:AC自动机;

关于AC自动机的知识:

该儿子不一定是真正的儿子,当没有这个儿子时(没有这条树边),ch就表示转移边。

对于Ttire树中的每个节点,都有一条回跳边,指最长后缀;

                         都有26条(树边+转移边),指重新匹配的最短路(当前再往下已经不匹配了);

KMP模板:F03【模板】KMP 算法 - 董晓 - 博客园 (cnblogs.com)

AC自动机模板: F08【模板】AC自动机

代码:(KMP,AC自动机 抄的模板)

#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
#include<map>
#include<cstdio>
using namespace std;
const int N=100010;
int ch[N][26],cnt[N],ne[N]; //转移边 是否是串的结尾 回跳边  
int idx;
vector<int>tpid[N];
void insert(char *s,int tp){//建树
  int p=0;
  for(int i=0;s[i];i++){
    int j=s[i]-'a';
    if(!ch[p][j])ch[p][j]=++idx;
    p=ch[p][j];
  }
  cnt[p]++;
  tpid[p].push_back(tp);
}
void build(){//建AC自动机
  queue<int> q;
  for(int i=0;i<26;i++)
    if(ch[0][i])q.push(ch[0][i]);
  while(q.size()){
    int u=q.front();q.pop();
    for(int i=0;i<26;i++){
      int v=ch[u][i];
      if(v)ne[v]=ch[ne[u]][i],q.push(v);
      else ch[u][i]=ch[ne[u]][i];
    }
  }
}
int main(){
	freopen("D:\\1011(2).txt", "w", stdout);
	int t; cin>>t;
	while(t--){
		idx=0;
		memset(ch,0,sizeof(ch));
    	memset(cnt,0,sizeof(cnt));
    	memset(ne,0,sizeof(ne));
    	for(int op=0;op<N;op++)
        	tpid[op].clear();
		int n; scanf("%d",&n);
		map<int,int>ans;
		char a[100010],c[10010];
		int nex[10010];
		memset(nex,0,sizeof(nex));
		scanf("%s %s",a,c+1);
		int lenc=strlen(c+1);
		nex[1]=0;
		// 计算next函数
		for(int i=2,j=0;i<=lenc;i++){
			while(j&&c[i]!=c[j+1]) j=nex[j];
			if(c[i]==c[j+1]) j++;
			nex[i]=j;
		}
		for(int tp=1;tp<=n;tp++){
			char b1[10010],b2[100010];
			scanf("%s %s",b1,b2+1);
			int lenb2=strlen(b2+1);
			//KMP匹配 
			for(int i = 1, j = 0; i <= lenb2; i ++){
                while(j && b2[i] != c[j+1]) j = nex[j];
                if(b2[i] == c[j+1]) j ++;
                if(j == lenc){
                   insert(b1,tp); break;
		    	}
		    }
        }
        build();
        for(int k=0,i=0;a[k];k++){
            i=ch[i][a[k]-'a'];
            for(int j=i;j&&~cnt[j];j=ne[j])
             {
             	for(auto op:tpid[j])
             	    ans[op]=1;
             	cnt[j]=-1;
			 }
        }
       for(auto &op:ans)
          printf("%d ",op.first);
        printf("\n");
	}
	return 0;
}

                      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值