【原创】2019.10.09模拟赛 密码 独立集 益智游戏

原创

密码

题目描述

假发通过了不懈的努力,得到了将军家门锁的密码(一串小写英文字母)。但是假发被 十四和猩猩他们盯上了,所以假发需要把密码传递出去。因为假发不想十四他们发现几松门 前贴的小纸条就是将军家的密码,所以他加密了密码(新八:听起来有点诡异)。加密方法 如下:随机地,在密码中任意位置插入随机长度的小写字符串。 不过,假发相信银桑和他那么多年小学同学,一定能猜中密码是什么的(新八:银桑什 么时候成攮夷志士了!!!)。可是,写完了小纸条之后,假发觉得有点长,就想截去头和 尾各一段(可以为空),让剩下的中间那一段依然包含真~密码。想着想着,假发就想知道 有多少种可行方案。结果在沉迷于稿纸之际,假发被投进了狱门岛(新八:……)。于是, 就由你计算了。

输入格式

两行非空字符串,纯小写英文字母,第一行是加密后的密码,第二行是原密码。 第一行长度不超过300000,第二行不超过200。

输出格式

一行,有多少种方案。注意:不剪也是一种方案。

输入样例

abcabcabc
cba

输出样例

9

提示

用(L,R)表示一种方案,其中L和R分别表示截去头和尾的长度。这9钟方案分别是 (0,0),(0,1),(0,2),(1,0),(1,1),(1,2),(2,0),(2,1),(2,2)。

30%的数据满足第一行长度不超过1000。 100%的数据满足第一行长度不超过300000,方案总数不超过10^18。

分析

我真的很反感这些拿暴力冠冕堂皇做正解的人。

题目是什么意思呢?
给你一个长一点的字符串a和一个短一点的字符串b,你可以删去a的一个前缀一个后缀,使得b仍然是a的一个子序列,问方案数。

怎么做?

大家看一眼我的代码就懂了我的做法了,真的太暴力了不想讲。

首先我们找到a里面第一个 b 1 b_1 b1的位置,记为 c 1 c_1 c1吧,然后我们再继续向后依次找到 c 2 , c 3 , ⋯   , c l e n b c_2,c_3,\cdots,c_{len_b} c2,c3,,clenb,其实 c 2 , c 3 , ⋯   , c l e n b − 1 c_2,c_3,\cdots,c_{len_b-1} c2,c3,,clenb1都是没用的,只是为了保证 b b b [ c 1 , c l e n b ] 的 a [c_1,c_{len_b}]的a [c1,clenb]a中存在。
然后 a n s + = ( c 1 ) ∗ ( l e n a − c l e n b + 1 ) ; ans+=(c_1)*(len_a-c_{len_b}+1); ans+=(c1)(lenaclenb+1);(注意,删去长度为0的前后缀也是一种方案)
然后我们继续向后,重新求一遍 c 1 , c 2 , c 3 , ⋯   , c l e n b c_1,c_2,c_3,\cdots,c_{len_b} c1,c2,c3,,clenb。但是记住,算答案的时候,你的 c 1 c_1 c1 c l e n b c_{len_b} clenb都在右移,也就是说,你的 c 1 c_1 c1 l e n a − c l e n b + 1 len_a-c_{len_b}+1 lenaclenb+1是会重复计算的,因此你必须要有一方不能反复计算(说的很绕,看代码就能瞬间明白)。
或者你可以干脆找到最后一组 c c c

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
 
int a,b;
long long ans;
char arr[302030],brr[222];
 
int main()
{
    scanf("%s %s",arr+1,brr+1);
    a=strlen(arr+1),b=strlen(brr+1);
    int now=0,las=1,i=1,j=1;
    while(i<=a)
    {
        while(i<=a && arr[i]!=brr[j]) i++;
        if(i>a) break;
        if(j==1) las=now,now=i;
        if(j==b) ans+=1ll*(now-las)*(a-i+1);
        i++,j++;
        if(j>b) j=1,i=now+1;
    }
    printf("%lld\n",ans);
}

独立集

题目描述

有一天,一个名叫顺旺基的程序员从石头里诞生了。又有一天,他学会了冒泡排序和独 立集。在一个图里,独立集就是一个点集,满足任意两个点之间没有边。于是他就想把这两 个东西结合在一起。众所周知,独立集是需要一个图的。那么顺旺基同学创造了一个算法, 从冒泡排序中产生一个无向图。

这个算法不标准的伪代码如下:

void bubblesortgraph(n,a[])
//输入:点数n,1到n的全排列a
//输出:一个点数为n的无向图G
{ 
创建一个有n个点,0条边的无向图G。
	do{ 
		swapped=false
		for i 从1 到n-1
			if(a[i]>a[i+1])
			{
				在G中连接点a[i]和点a[i+1]
				交换a[i]和a[i+1]
				swapped =true
		}
	}while(swapped);
输出图G。
}
结束。

那么我们要算出这个无向图G最大独立集的大小。但是事情不止于此。顺旺基同学有时 候心情会不爽,这个时候他就会要求你再回答多一个问题:最大独立集可能不是唯一的,但 有些点是一定要选的,问哪些点一定会在最大独立集里。今天恰好他不爽,被他问到的同学 就求助于你了。

输入格式

输入包含两行,第一行为N,第二行为1 到N 的一个全排列。

输出格式

输出包含两行,第一行输出最大独立集的大小,第二行从小到大输出一定在最大独立集 的点的编号。

输入样例

3
3 1 2

输出样例

2
2 3

提示

【数据范围】
30%的数据满足N<=16
60%的数据满足N<=1,000
100%的数据满足N<=100,000

分析

容易发现,独立集就是上升子序列,最大独立集就是最长上升子序列。
哪些点最长上升子序列是必须选的?

首先这个点至少得在一条最长上升子序列上。怎么判断?
首先我们求出最长上升子序列长度 l e n len len
在这里插入图片描述

于是我们求出以每个点为起点,向左走的最长下降子序列长度 l 1 l_1 l1和向右走的最长上升子序列 l 2 l_2 l2,当且仅当 l 1 + l 2 = l e n + 1 ( 或 者 − 1 , 看 你 把 不 把 这 个 点 算 入 l 1 , l 2 中 ) l_1+l_2=len+1(或者-1,看你把不把这个点算入l_1,l_2中) l1+l2=len+1(1l1,l2)时,它在一条最长上升子序列中。

而哪些点是必须选的?
就是那些 l 1 , l 2 l_1,l_2 l1,l2独一无二的点。
为什么?
比方说我们已经造出了一个长为 l x l_x lx的上升子序列,现在我们能且只能找一个 l 1 = l x + 1 l_1=l_x+1 l1=lx+1的数加进来,而如果有这个 l 1 l_1 l1的数不止一个,它们就不是必须选的。
l 2 l_2 l2呢?
你都 l 1 + l 2 = l e n + 1 l_1+l_2=len+1 l1+l2=len+1了,你现在用 l 1 l_1 l1就等于在用 l 2 l_2 l2

#include<map>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
 
inline void Read(int &p)
{
    p=0;
    char c=getchar();
    while(c<'0' || c>'9') c=getchar();
    while(c>='0' && c<='9')
        p=p*10+c-'0',c=getchar();
}
 
const int MAXN=102030;
map<pair<int,int>,int> M;
int n,tmp,len,nel,dp[MAXN],pd[MAXN],arr[MAXN],rra[MAXN],lef[MAXN],rig[MAXN];
 
int main()
{
    Read(n);
    for(int i=1;i<=n;i++) Read(arr[i]),rra[n-i+1]=-arr[i];
     
    lef[1]=1,rig[1]=1,dp[1]=arr[1],pd[1]=rra[1],len=1,nel=1;    
    for(int i=2;i<=n;i++)
        if(arr[i]>dp[len]) dp[++len]=arr[i],lef[i]=len;
        else dp[tmp=lower_bound(dp+1,dp+len+1,arr[i])-dp]=arr[i],lef[i]=tmp; 
    for(int i=2;i<=n;i++)
        if(rra[i]>pd[nel]) pd[++nel]=rra[i],rig[i]=nel;
        else pd[tmp=lower_bound(pd+1,pd+nel+1,rra[i])-pd]=rra[i],rig[i]=tmp;
 
    printf("%d\n",len);
    for(int i=1;i<=n;i++) if(lef[i]+rig[n-i+1]-1==len) M[make_pair(lef[i],rig[n-i+1])]++;
    for(int i=1;i<=n;i++) if(M[make_pair(lef[i],rig[n-i+1])]==1) printf("%d ",i);
}

益智游戏

题目描述

小P 和小R 在玩一款益智游戏。游戏在一个正权有向图上进行。 小P 控制的角色要从A 点走最短路到B 点,小R 控制的角色要从C 点走最短路到D 点。 一个玩家每回合可以有两种选择,移动到一个相邻节点或者休息一回合。 假如在某一时刻,小P 和小R 在相同的节点上,那么可以得到一次特殊奖励,但是在每 个节点上最多只能得到一次。 求最多能获得多少次特殊奖励

输入格式

第一行两个整数n,m 表示有向图的点数和边数。 接下来m 行每行三个整数xi,yi,li,表示从xi 到yi 有一条长度为li 的边。 最后一行四个整数A,B,C,D,描述小P 的起终点,小R 的起终点。

输出格式

输出一个整数表示最多能获得多少次特殊奖励。若小P 不能到达B 点或者小R 不能到达 D 点则输出-1。

输入样例

5 5
1 2 1
2 3 2
3 4 4
5 2 3
5 3 5
1 3 5 4

输出样例

2

提示

对于30%的数据,满足n≤50
对于60%的数据,满足n≤1000,m≤5000
对于100%的数据,满足n≤50000,m≤200000,1≤li≤500000000

分析

在这里插入图片描述

如上图,我们考虑最短路之间的交点的位置关系,发现,根本不可能有上图的“最短路之间像十字绣一样穿入又穿出”,因为这一条 A B \color{red}{AB} AB和你那支出去的 A B AB AB,他们如果都是最短的话,不如大家都走 A B \color{red}{AB} AB,这样比较好看, B C BC BC C D CD CD同理,这样我们的“特殊奖励”还会多一点。

在这里插入图片描述

假如我们把其中一条反过来,现在就不存在什么合并了。
我们发现,我们最多交于一处连续的“特殊奖励”点,因为我们的方向不同,所以不管怎么等,我们这次见面分开以后,再怎么等也不能再见面了,呜呜呜。

于是我们得出结论,特殊奖励的点总是连续的
有什么用到时候再说。

下面的问题是我们怎么找最短路上的边?
因为最短路肯定不止一条嘛。

首先我们跑四次最短路, A → B 的 最 短 路 记 为 d i s 1 A\to B的最短路记为dis_1 ABdis1 C → D 的 最 短 路 记 为 d i s 2 C\to D的最短路记为dis_2 CDdis2,同时建反图,在上面跑 B → A 的 最 短 路 记 为 d i s 3 B\to A的最短路记为dis_3 BAdis3 D → C 的 最 短 路 记 为 d i s 4 D\to C的最短路记为dis_4 DCdis4。(当然 d i s 1 = d i s 3 , d i s 2 = d i s 4 dis_1=dis_3,dis_2=dis_4 dis1=dis3,dis2=dis4啦)
同时我们也会求出正图上 A → 每 个 点 i 的 最 短 路 d i s 1 [ i ] A\to每个点i的最短路 dis_1[i] Aidis1[i] C 的 d i s 2 [ i ] C的dis_2[i] Cdis2[i] B 的 d i s 3 [ i ] B的dis_3[i] Bdis3[i] D 的 d i s 4 [ i ] D的dis_4[i] Ddis4[i]
再定义 d i s ( i , j ) dis(i,j) dis(i,j)代表 i → j i\to j ij的边的长度,如果没有边就是0。

怎么看一个点或者一条边在不在最短路上呢?
对于点 i i i,我们枚举一个中转点 j j j,如果有 d i s 1 [ i ] + d i s ( i , j ) + d i s 3 [ j ] = d i s 1 dis_1[i]+dis(i,j)+dis_3[j]=dis_1 dis1[i]+dis(i,j)+dis3[j]=dis1,就说明 i → j i\to j ij是一条 A → B A\to B AB的最短路上的一条边,当然如果有 d i s 2 [ i ] + d i s ( i , j ) + d i s 4 [ j ] = d i s 2 dis_2[i]+dis(i,j)+dis_4[j]=dis_2 dis2[i]+dis(i,j)+dis4[j]=dis2,就说明边 i → j i\to j ij在某条 C → D C\to D CD的最短路上。
(实际上我们是先枚举j,再跑所有邻接边,来看这些边是否满足上述条件,时间复杂度是 Θ ( m ) \Theta(m) Θ(m)的)

我们把所有最短路上的边拿出来建了一个新图,又因为上面那个加粗的结论,所以答案就是这个图上的最长路。
这个问题是拓扑排序的经典问题,随便玩玩就是了。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
  
inline void Read(int &p)
{
    p=0;
    char c=getchar();
    while(c<'0' || c>'9') c=getchar();
    while(c>='0' && c<='9')
        p=p*10+c-'0',c=getchar();
}
  
typedef long long ll;
const int MAXN=52030,MAXM=202030,inf=1047483647;
  
bool vis[MAXN];
priority_queue<pair<int,int> > Q;
int n,m,u,v,w,A,B,C,D,cnt[4],head[4][MAXN],nxt[4][MAXM],to[4][MAXM],val[4][MAXM],dis[5][MAXN];
  
inline void addedge(int u,int v,int w,int z)
{
    nxt[z][++cnt[z]]=head[z][u],head[z][u]=cnt[z],to[z][cnt[z]]=v,val[z][cnt[z]]=w;
}
  
inline void dijkstra(int s,int z,int k)
{
    while(!Q.empty()) Q.pop();
    for(int i=1;i<=n;i++) dis[k][i]=inf,vis[i]=1;
    dis[k][s]=0,Q.push(make_pair(0,s));
      
    while(!Q.empty())
    {
        u=Q.top().second,Q.pop();
        if(!vis[u]) continue; vis[u]=0;
        for(int i=head[z][u];i;i=nxt[z][i])
            if(dis[k][to[z][i]]>dis[k][u]+val[z][i])
                dis[k][to[z][i]]=dis[k][u]+val[z][i],Q.push(make_pair(-dis[k][to[z][i]],to[z][i])); 
    }
}
queue<int>q;
int ans,cnt_,f[MAXN],ind[MAXN],head_[MAXN],nxt_[MAXM],to_[MAXM];
inline void edgeadd(int u,int v) 
{
    nxt_[++cnt_]=head_[u],head_[u]=cnt_,to_[cnt_]=v;
}
  
int main()
{
    Read(n),Read(m);
      
    for(int i=1;i<=m;i++) Read(u),Read(v),Read(w),addedge(u,v,w,0),addedge(v,u,w,1);
    Read(A),Read(B),Read(C),Read(D);
      
    dijkstra(A,0,0),dijkstra(B,1,1),dijkstra(C,0,2),dijkstra(D,1,3);
    if(dis[0][B]==inf || dis[2][D]==inf) return puts("-1")*0;
          
    for(int u=1;u<=n;u++)
        for(int i=head[0][u];i;i=nxt[0][i])
            if(dis[0][u]!=inf && dis[1][to[0][i]]!=inf && dis[2][to[0][i]]!=inf && dis[3][u]!=inf)
                if(dis[0][u]+val[0][i]+dis[1][to[0][i]]==dis[0][B] && dis[2][u]+val[0][i]+dis[3][to[0][i]]==dis[2][D])
                    edgeadd(u,to[0][i]),ind[to[0][i]]++;
  
    for(int i=1;i<=n;i++)
        if(!ind[i] && dis[0][i]+dis[1][i]==dis[0][B] && dis[2][i]+dis[3][i]==dis[2][D]) q.push(i),f[i]=1;
    while(!q.empty())
    {
        int pos=q.front(); q.pop(),ans=max(ans,f[pos]);
        for(int i=head_[pos];i;i=nxt_[i])
        {
            f[to_[i]]=max(f[to_[i]],f[pos]+1),ind[to_[i]]--;
            if(!ind[to_[i]]) q.push(to_[i]);
        }
    }
    printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值