I do hope for world peace...|

weirdoX

园龄:2年6个月粉丝:3关注:10

CF 2004 Educational Codeforces Round 169 (Rated for Div. 2)

E - Not a Nim Problem

发现就是标准 Nim 游戏,
然后考虑如何求 SG 函数。
观察发现有:

\[\begin{align} g_0=0\\ g_1=1\\ g_2=0\\ g_3=2 \end{align} \]

然后这里先说结论,在说证明(赛时通过打表找规律)
对于一个数字 \(i\),如果 \(i\) 是偶数,那么 \(g_i=0\),如果 \(i\) 是奇合数,假设它的最小质因子为 \(p\),那么有 \(g_i=g_p\),如果 \(i\) 是奇质数,那么 \(g_i=id\)\(id\) 表示它是除了 \(2\) 以外的第几个质数。

证明:

首先对于 \(i\le 2\) 成立。
考虑 \(i+1\)

  • \(i+1\) 是质数,其是第 \(id\) 个质数:
    \([1,i]\) 就是它的后继状态,那么显然 \(g_2=0\),然后 \([1,id-1]\) 一定在前面出现过。所以 \(g_{i+1}=id\)
  • \(i+1\) 是偶数:
    显然对于一个数 \(c\),如果 \(\gcd(i,c)=1\) 那么 \(c\) 一定是奇数,\(i+1-c\) 就一定是奇数,\([1,i]\) 中的奇数都不为 \(0\),所以 \(g_{i+1}=0\)
  • \(i+1\) 是奇合数:
    某个质因子是 \(p\),那么显然 \(p\) 的倍数一定不能减去,并且 \([1,i]\) 中满足 \(g_j=g_p\) 的一定有 \(p\mid j\),所以 \(g_{i+1}<g_p\),然后 \(g_p\) 最小的那个就是最小质因子的 SG。
    证毕。

然后直接线性筛法即可。
时间复杂度:\(O(n+V)\)

Code

#include <bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for (int i = (l); i <= (r); i++)
#define per(i,r,l) for (int i = (r); i >= (l); i--)
#define pb push_back
#define eb emplace_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
#define maxn(a, b) a = max(a, b)
#define minn(a, b) a = min(a, b)
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
typedef double db;
const ll mod = 998244353;
mt19937 gen(114514);
ll rp(ll a,ll b) {ll res=1%mod;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}

const int N = 1e7 + 1;
int sg[N];
bool b[N];

void preinit() {
    int idx = 0;
    sg[1] = ++idx;
    VI p;
    rep(i,2,N-1) {
        if (!b[i]) {
            p.pb(i);
            if (i!=2) sg[i]=++idx;
        }
        for(auto x:p){
            if (x*1ll*i>=N)break;
            b[x*i]=true;
            sg[x*i]=sg[x];
            if (i%x==0) break;
        }
    }
}

void solve() {
    // init();
    int n, res = 0;
    scanf("%d", &n);
    rep(i,1,n) {
        int a;
        scanf("%d", &a);
        res ^= sg[a];
    }
    puts(res ? "Alice" : "Bob");
}

int main() {
    preinit();
    int T;
    scanf("%d", &T);
    while (T--)
        solve();
    return 0;
}

F. Make a Palindrome

首先考虑对于一个序列直接怎么 \(O(n)\)\(f\) 值,就发现考虑维护两个指针 \(l,r\),如果 \(a_l=a_r\),则 \(l+1,r-1\),否则我们就让小的那一个分裂,那么每次操作一定可以减少长度,所以最优。
然后就可以 \(O(n^3)\),考虑换一种可优化的方式计算 \(f\),通过猜想大概就是看一下前缀后缀集合有多少个不相等的数字。
发现能够过拍。

考虑优化,我们考虑枚举区间,区间的和为 \(val\),且这个区间作为某个前缀,其左端点为 \(l\),右端点为 \(r\)
问题就是找有多少个 \(j(j\ge l)\) 满足:
\(\forall i(l\le i)~ s_{i,j} \ne val\)
于是我们考虑枚举了 \(l,r\) 之后看看有多少个 \(j\) 不满足条件即可。

然后发现某一个 \(j\) 所对应的 \(i\) 也一定只有至多一个满足 \(s_{i,j}=val\)
问题就是问有多少个区间 \([i,j]\) 满足和 \(s_{i,j}=s_{l,r},(l\le i,r\le j)\)
于是我们用 map 维护一个 int, vector<pair<int, int>> 表示值为 \(val\) 对应的区间。
然后按照左端点排序,对于每个区间看看它后面有多少个右端点大于它的右端点即可。
用树状数组维护。
时间复杂度:\(O(n^2\log n)\)

Code:

#include <bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for (int i = (l); i <= (r); i++)
#define per(i,r,l) for (int i = (r); i >= (l); i--)
#define pb push_back
#define eb emplace_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
#define maxn(a, b) a = max(a, b)
#define minn(a, b) a = min(a, b)
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
typedef double db;
const ll mod = 998244353;
mt19937 gen(114514);
ll rp(ll a,ll b) {ll res=1%mod;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
template<class T>
struct BIT {
    vector<T> bit;
    int size;
    void init(int n) {
        bit.clear(); bit.resize(n + 1, 0);
        size = n;
    }
    void update(int x, T v) {
        assert(x);
        while (x <= size) {
            bit[x] += v;
            x += (x & -x);
        }
    }

    T query(int x) {
        assert(x <= size);
        T res = 0;
        while (x) {
            res += bit[x];
            x -= (x & -x);
        }
        return res;
    }

    int find(T k) {
        assert(k); k--;
        int x = 0;
        for (int i = log2(size); i >= 0; i--) {
            int y = x + (1 << i);
            if (y <= size && bit[y] <= k) {
                x = y;
                k -= bit[y];
            }
        }
        return x + 1;
    }
};
BIT<int> t;
const int N = 2043;
int n;
int a[N];
map<int, vector<PII>> p;

void solve() {
    // init();
    scanf("%d", &n);
    rep(i,1,n) scanf("%d", &a[i]);
    ll ans = 0;

    p.clear();
    per(i,n,1) {
    	int sum = 0;
    	rep(j,i,n) {
    		sum += a[j];
    		p[sum].eb(i, j);
    	}
    }
    for (auto [val, ve] : p) {
        t.init(n);
        // cout << "val: " << val << endl;
    	for (auto [l, r] : ve) {
            ans += n - r - t.query(n - r + 1);
            // cout << l << ' ' << r << endl;
            t.update(n - r + 1, 1);
    	}
        // cout << ans << endl;
    }
    printf("%lld\n", ans);
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--)
        solve();
    return 0;
}


update:
发现有更加简单的做法,仔细考虑区间的性质,发现一定不会有包含关系,所以随便乱搞计数即可:

Code:

#include <bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for (int i = (l); i <= (r); i++)
#define per(i,r,l) for (int i = (r); i >= (l); i--)
#define pb push_back
#define eb emplace_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
#define maxn(a, b) a = max(a, b)
#define minn(a, b) a = min(a, b)
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
typedef double db;
const ll mod = 998244353;
mt19937 gen(114514);
ll rp(ll a,ll b) {ll res=1%mod;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}

const int N = 2043;
int n;
int a[N];
map<int, vector<PII>> p;

void solve() {
    // init();
    scanf("%d", &n);
    rep(i,1,n) scanf("%d", &a[i]);
    p.clear();
    per(i,n,1) {
    	int sum = 0;
    	rep(j,i,n) {
    		sum += a[j];
    		p[sum].eb(i, j);
    	}
    }
    ll ans = 0;
    rep(i,1,n) ans += i * (n - i + 1);
    for (auto [val, ve] : p) {
        int sz = SZ(ve);
        ans -= sz * 1ll * (sz + 1) / 2;
    }
    printf("%lld\n", ans);
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--)
        solve();
    return 0;
}

G. Substring Compression

首先容易证明第偶数个数字一定是一位数。

证明:
假设这一对相邻的元素为 \(x,y\),那么对应的两种方案就是:
\(len\times x\)\((len-1)\times(10y+x)\)
然后这里有 \(len>1\)
于是我们作一下差:
\(\Delta = (len-x)\times(10y+x)-len\times x = 10y(len-1)-x\)
显然 \(len-1>0\),然后 \(10y>x\)
所以 \(len\times x\) 长度更小,更优,可以直接替换。

然后我们得到一个 \(O(n^2)\) 算一个序列的 \(f\) 值的方法:
\(dp_i\) 表示上一个选择的长度在 \(i\) 这个位置。
然后:

\[dp_i=\min_{j<i-1}(dp_j+s_i\times(i-j-1)) \]

考虑将式子拆一下:

\[dp_i=\min(dp_j-s_i\times j + s_i\times(i-1)) \]

于是我们把 \(s_i\) 记入状态中:
\(f_{i,c}\) 表示目前选的翻倍长度为 \(c\),然后考虑到了第 \(i\) 位的最小长度。

\[\begin{align} f_{i,c}\to f_{i+1,c}+c\\ f_{i,c}\to f_{i+2,s_{i+1}+s_{i+1}} \end{align} \]

然后需要支持端点插入删除,求区间最小值。
EDU 题解里面有一个逆天做法。感觉非常不会,题解链接

但是不会没有关系,发现这篇题解做法非常的好。
我们考虑预处理出每个 \(k-1\) 的块的前缀后缀积,那么对于任意长度为 \(k\) 的连续段的矩阵积一定可以通过两端的后缀前缀拼起来。
然后还有一个小问题,矩阵只能 \(i\to i+1\) 的转移,那么对于第二个向 \(i+2\) 转移的式子怎么处理呢?
我们考虑新开一个状态:\(f_{i,0}\) 表示目前考虑前 \(i\) 个数字,从 \(i+1\) 开始强制重新开启新的一段的最小值。那么有:

\[\begin{align} f_{i,c}\to f_{i+1,0}+c\\ f_{i,0}\to f_{i+1,s_{i+1}} \end{align} \]

这样就解决了。
时间复杂度:\(O(n\times10^3)\)

Code:

#include <bits/stdc++.h>
using namespace std;

const int inf = 1e9;
const int N = 200010;

struct mat {
	array<array<int, 10>, 10> v;

	mat(int val = inf) {
		for (int i = 0; i < 10; i++)
			v[i].fill(val);
	}

} a[N], pre[N], suf[N];

int n, k;
char t[N];

mat mul(const mat &a, const mat &b) {
	mat c;
	for (int i = 0; i < 10; i++) {
		for (int k = 0; k < 10; k++) {
			for (int j = 0; j < 10; j++) {
				c.v[i][j] = min(c.v[i][j], a.v[i][k] + b.v[k][j]);
				c.v[i][j] = min(c.v[i][j], a.v[i][k] + b.v[k][j]);
			}
		}
	}
	return c;
}


int main() {
	scanf("%d%d", &n, &k);
	scanf("%s", t + 1);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j < 10; j++) {
			a[i].v[j][j] = a[i].v[j][0] = j;
		}
		a[i].v[0][t[i] - '0'] = 0;
	}
	for (int i = 1; i <= n; i++) {
		pre[i] = a[i];
		if (i % k != 1) {
			pre[i] = mul(pre[i - 1], pre[i]);
		}
	}
	for (int i = n; i >= 1; i--) {
		suf[i] = a[i];
		if (i % k && i < n) {
			suf[i] = mul(suf[i], suf[i + 1]);
		}
	}

	for (int i = k; i <= n; i++) {
		mat f;
		f.v[0][0] = 0;
		f = mul(f, suf[i - k + 1]);
		if (i % k) f = mul(f, pre[i]);
		printf("%d ", f.v[0][0]);
	}
	puts("");
	return 0;
}

本文作者:weirdoX

本文链接:https://www.cnblogs.com/weirdoX/p/18367049

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   weirdoX  阅读(134)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起