1. 程式人生 > 實用技巧 >[hdu-6865]Kidnapper's Matching Problem 線性基+KMP 2020多校8

[hdu-6865]Kidnapper's Matching Problem 線性基+KMP 2020多校8

【題目連結】http://acm.hdu.edu.cn/showproblem.php?pid=6865

【題意】

給出兩個陣列A B 和集合S,分別有n,m,k個數,n(1n210^5),m(1mmin(n,510^4)) andk(1k100),

求表示式

從n中取長度為m的連續一段,每個數字與對應m中的數異或 值如果都在 S集合的異或張成中(張成:一個集合中的數 其異或和的所有可能的結果組成的集合),則matched為1,否則為0.

【題解】

先求出S集合的線性基,然後對A,B集合中的數,消去對應線性基中的位。

則剩下的數如果想滿足異或後在S集合中,必須相等。因為餘下的位已經無法用線性基表示。

然後就變成了找A串中的B串,用KMP處理

官方證明:

【AC程式碼】

#include <bits/stdc++.h>
using namespace std;
int const maxn=2e5+7,mod=1e9+7;
const int MAXL = 29,maxn2=5e4+7;
int n,m,k,nex[maxn2];
bool res[maxn];
long long a[maxn],b[maxn2],s[maxn2];
struct LinearBasis{
    long long aa[MAXL+1];
    LinearBasis(){
        std::fill(aa, aa 
+ MAXL + 1, 0); } void insert(long long t){ for (int j = MAXL; j >= 0; j--){ if (!(t & (1ll << j))) continue; if (aa[j]) t ^= aa[j]; else { for (int k = 0; k < j; k++) if (t & (1ll << k)) t ^= aa[k];
for (int k = j + 1; k <= MAXL; k++) if (aa[k] & (1ll << j)) aa[k] ^= t; aa[j] = t; return; } } } void build(long long *x, int len){ std::fill(aa, aa + MAXL + 1, 0); for (int i = 1; i <= len; i++){ insert(x[i]); } } }ji;//線性基模板 int main(){ int t; scanf("%d",&t); for(int q=1;q<=t;q++){ scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); } for(int i=1;i<=m;i++){ scanf("%lld",&b[i]); } for(int i=1;i<=k;i++){ scanf("%lld",&s[i]); } ji.build(s,k); //對每一位消去線性基中的位 for(int i=1;i<=n;i++){ for(int j=MAXL;j>=0;j--)if(a[i]>>j&1)a[i]^=ji.aa[j]; } for(int i=1;i<=m;i++){ for(int j=MAXL;j>=0;j--)if(b[i]>>j&1)b[i]^=ji.aa[j]; } long long ans=0; //KMP模板 nex[1]=0; for(int i=2,j=0;i<=m;i++){ while(j&&b[j+1]!=b[i])j=nex[j];//如果字首匹配不上就一直往前跳到能匹配的字首,要比只條一次快 if(b[j+1]==b[i])j++; nex[i]=j; } memset(res,0,n); for(int i=1,j=0;i<=n;i++){ while(j&&b[j+1]!=a[i])j=nex[j]; if(b[j+1]==a[i])j++; if(j==m){ res[i-m]=1; j=nex[j]; } } for(int i=n-m;i>=0;i--)ans=(ans*2+res[i])%mod; printf("%lld\n",ans); } }