• 「WC2016」论战捆竹竿


    「WC2016」论战捆竹竿

    前置知识

    参考资料:《论战捆竹竿解题报告—王鉴浩》,《字符串算法选讲—金策》。

    Border&Period

    若前缀 (pre(s,x)​) 与后缀 (suf(s,n-x-1)​) 相等,则 (pre(s, x)​)(s​) 的一个 ( ext{Border}​)

    (x​)(s​) 的一个周期 (( ext{Preiod}​)) 满足 (s[i]=s[i+x],forall{1leq ileq|s|-x}​)

    对于一个 ( ext{Border } pre(s, x)​) ,满足 (|s|-x​)(s​) 的一个周期。

    (s​) 最长的 (Boder​)(pre(s, x)​) ,则 (s​)( ext{Border}​) 数量为 (pre(s, x)​)( ext{Border}​) 数量 (+1​)

    Periodicity Lemma

    (p, q​)(s​) 的周期,且满足 (p+q+gcd(p,q)leq |s|​) ,则 (gcd(p,q)​) 也是 (s​) 的一个周期。

    Borders 的性质

    引理:对于长度 (geq​) (frac{|s|}{2}​)(s​)( ext{Border}​) ,其长度组成一个等差数列。

    证明:假设有满足条件的 ( ext{Boder}​) 集合 (a,forall a_i geq frac{|s|}{2}​) ,可以得到周期集合 (b, forall b_ileq frac{|s|}{2}​)

    根据 Periodicity Lemma(b) 的所有元素的 (gcd) 也是 (s) 的一个周期,显然 (a) 集合能组成公差为 (g) 的等差数列。

    推论:字符串 (s) 的所有 $ ext{Border} $ 可以分成 (O(log|S|)) 段等差数列。

    根据引理, (s​) 所有长度 (geq frac{|s|}{2}​)( ext{Border}​) 可以分成一个等差数列 ,设 (s​) 最长的不超过 (frac{|s|}{2}​)( ext{Border}​)(pre(s, m)​) ,记 (T(n)​)(pre(s, n)​)( ext{Border}​) 分成的等差数列数量,显然有

    [T(n)leq T(m)+ 1, T(n)=O(log n) ]

    同理,字符串所有周期也可以被分成 (O(log|S|)) 段等差数列。

    解题思路

    先转化一下题意,一开始字符串长度为 (n) ,每次可以接上初始字符串的一个周期或者初始字符串本身,求在 (w) 范围内能得到的字符串的长度种类数。

    先不考虑 Border&Period 的性质,可以直接转化为一个模 (n) 意义下的最短路问题,复杂度 (O(n^2))

    前置知识 可以得知,每次加的长度可以分成 (O(log n)) 段等差数列,考虑对等差数列内部怎么快速计算。

    对于一个项数为 (m) ,首项为 (v) ,公差为 (d) 的等差数列,把转移放到模 (v) 意义下做,每次转移就相当与加若干个 (d) ,并且考虑转移是若干个互不相交的环,对于一个环可以发现,当前环内部最小的一位肯定不会被更新。所以可以从这一位开始依次更新整个环。

    单独考虑一个环,令 (i,j) 为展开后环上第 (i) 个元素和第 (j) 个元素,(j) 能更新 (i) 当且仅当 (j < i)(i-j < m) ,这个东西可以直接用单调队列维护。

    接下来考虑将两段等差数列的贡献合并,只需要将维护的东西从模一个首项意义转移到另外一个即可,那么只需要对应的位置更新完后,再做一遍长度为之前首项的转移即可,这样子就大概做完了,总复杂度 (mathcal O(nlog n))

    code

    /*program by mangoyang*/
    #include<bits/stdc++.h>
    #define inf ((ll)0x3f3f3f3f3f3f3f3f)
    #define Max(a, b) ((a) > (b) ? (a) : (b))
    #define Min(a, b) ((a) < (b) ? (a) : (b))
    typedef long long ll;
    using namespace std;
    template <class T>
    inline void read(T &x){
        int ch = 0, f = 0; x = 0;
        for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
        for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
        if(f) x = -x;
    }
    const int N = 500005;
    char s[N]; 
    int a[30][N];
    ll dp[N], q[N], g[N], w, ans;
    int b[N], id[N], vis[N], nxt[N], n, tot;
    inline void gao(int *a, int len, int lst){
    	int v = a[1], d = len > 1 ? a[2] - a[1] : 1;
    	for(int i = 0; i < v; i++) g[i] = inf, vis[i] = 0;
    	for(int i = 0; i < lst; i++) g[dp[i]%v] = Min(g[dp[i]%v], dp[i]);
    	for(int i = 0; i < v; i++) dp[i] = g[i];
    	for(int i = 0; i < v; i++) if(!vis[i]){
    		ll mn = dp[i]; int pos = i; vis[i] = 1;
    		for(int j = (i + lst) % v; j != i; j = (j + lst) % v){
    			if(dp[j] < mn) mn = dp[j], pos = j;
    			vis[j] = 1;
    		}
    		dp[(pos+lst)%v] = Min(dp[(pos+lst)%v], dp[pos] + lst);
    		for(int j = (pos + lst) % v; j != pos; j = (j + lst) % v)
    			dp[(j+lst)%v] = Min(dp[(j+lst)%v], dp[j] + lst);
    	}
    	if(len == 1) return;
    	for(int i = 0; i < v; i++) vis[i] = 0;
    	for(int i = 0; i < v; i++) if(!vis[i]){
    		ll mn = dp[i]; int pos = i; vis[i] = 1;
    		for(int j = (i + d) % v; j != i; j = (j + d) % v){
    			if(dp[j] < mn) mn = dp[j], pos = j;
    			vis[j] = 1;
    		}
    		int h = 1, t = 1; q[1] = dp[pos], id[1] = 0;
    		for(int k = 1, j = (pos + d) % v; j != pos; j = (j + d) % v, ++k){
    			while(h <= t && k - id[h] >= len) ++h;
    			if(h <= t) dp[j] = Min(dp[j], q[h] + v + (k - id[h]) * d);
    			while(h <= t && q[t] - id[t] * d > dp[j] - k * d) --t;
    			q[++t] = dp[j], id[t] = k;
    		}
    	}
    }
    inline void solve(){
    	read(n), read(w); tot = 0;
    	scanf("%s", s + 1);
    	for(int i = 2, j = 0; i <= n; nxt[i++] = j){
    		while(j && s[j+1] != s[i]) j = nxt[j];
    		if(s[j+1] == s[i]) j++;
    	}
    	for(int x = nxt[n]; x; x = nxt[x]){
    		int flag = 0;
    		for(int i = 1; i <= tot; i++)
    			if(b[i] == 1 || a[i][2] - a[i][1] == (n - x) - a[i][b[i]]){
    				a[i][++b[i]] = n - x, flag = 1; break; 
    			}
    		if(!flag) ++tot, a[tot][b[tot]=1] = n - x;
    	}
    	dp[0] = n;
    	for(int i = 1; i < n; i++) dp[i] = inf;
    	int lst = n;
    	for(int i = 1; i <= tot; i++)
    		gao(a[i], b[i], lst), lst = a[i][1];
    	ans = 0;
    	for(int i = 0; i < lst; i++) if(dp[i] <= w) ans += (w - dp[i]) / lst + 1;
    	cout << ans << endl;
    }
    int main(){
    	int T; read(T); while(T--) solve();
    	return 0;
    }
    
  • 相关阅读:
    umeng社交分享最新版5.0的跨进程使用崩溃的问题及解法-Android
    AlertDialog禁止返回键
    一个男人想经商,不读 100本商人自传,怎么会了解商人的思维状态
    Android中使用Gson解析JSON数据的两种方法
    DevExpress gridControl控件动态绑定列 zt
    获得WCF Client端的本地端口 z
    log4net.dll配置以及在项目中应用 zt
    系统交易策略 hylt
    判斷作業系統為 64bit 或 32bit z
    路徑 z
  • 原文地址:https://www.cnblogs.com/mangoyang/p/10467065.html
Copyright © 2020-2023  润新知