P11230 [CSP-J 2024] 接龙
发现所有题解都是用dp来做的,而且还比较有难度。
这些状态和如何$ O(1) $转移其实还是有难度的,所以本蒟蒻来写一个伟大的cpp教的非dp算法。
首先发现问题的切入点在于 r 最大只有一百,思考如何按轮次顺序预处理出所有的情况。
在\(r=1\)时,寻找开头为一的长度在 \([2,k]\) 内的序列,将这些序列的末尾全部标记为1即可。
那么下一轮转移不就很简单了吗,只需要遍历所有的数字,如果这个值可以作为上一轮的末尾,那么将它后面的点再次标记。
但是问题在于我并不知道它的上一步是否和它属于同一个人,如果属于,那么这次操作是不可行的。
其实想解决这个问题并不难,我们可以分析一下每个点作为开头的情况:
1.上一步无法到达,不进行任何操作。
2.上一步仅有一个数列可以到达,判断是否在一列,若在,不操作,若在,操作。
3.上一步有两个不同数列可以到达,直接进行操作。
综上所述,仅需记录每一次是否能够达到,若仅有一个数列可以达到,记录下这一个数列。
那么我们不妨将 $ ans_{i,j} $设置为第i轮后末尾为j的情况,初始值设为-1,其中-1时表示无法到达,0表示有两个不同的数列可以到达,其他数字表示唯一一个可以到达的数列的编号。
但是每次找到一个点就要再次遍历k次后面的数,所以时间肯定是不够的。
不难发现出现了重复遍历,所以我们可以记一个变量 tmp 表示后面有多少个点在本轮中能够到达,每次发现一个点可以作为本轮的起点时,就将其设为 \(k-1\) ,如果k不为零,那就说明这个值在本轮可以作为答案,每次发现一个新答案后 tmp 减一即可。
之后就不必过多赘述了。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,k,q,r,c;
vector<int> e[100005];
int ans[105][200005];
void init(){
for(int i=1;i<=n;i++)e[i].clear();
memset(ans,-1,sizeof(ans));
}
void solve(){
for(int i=1,tmp;i<=n;i++){//第一轮
tmp=0;
for(int j:e[i]){
if(tmp){
tmp--;
if(ans[1][j]==-1)ans[1][j]=i;
else if(ans[1][j]!=i)ans[1][j]=0;
}
if(j==1)tmp=k-1;//记录后面是否存为答案,下同
}
}
for(int l=2;l<=100;l++){
for(int i=1,tmp;i<=n;i++){
tmp=0;
for(int j:e[i]){
if(tmp){
tmp--;
if(ans[l][j]==-1)ans[l][j]=i;
else if(ans[l][j]!=i)ans[l][j]=0;
}
if(ans[l-1][j]!=-1&&ans[l-1][j]!=i)tmp=k-1;
}
}
}
}
signed main(){
scanf("%lld",&t);
while(t--){
scanf("%lld%lld%lld",&n,&k,&q);
init();//cyt在次提醒您:数据千万条,清空第一条。多测不清空,暴零泪两行 。
for(int i=1,l,x;i<=n;i++){
scanf("%lld",&l);
while(l--){
scanf("%lld",&x);
e[i].push_back(x);//将每个数加入相应队列
}
}
solve();
while(q--){
scanf("%lld%lld",&r,&c);
if(ans[r][c]!=-1)printf("1\n");
else printf("0\n");
}
}
return 0;
}

浙公网安备 33010602011771号