Catcats Contest 系列

2021/4/19 18:58:22

本文主要是介绍Catcats Contest 系列,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

NOI 赛前抱 3 个月的佛脚(ku

完成度:5/17

Catcats Contest #1

字符串 (count)

神必转化,壬都傻了

假设一个串有 \(i\) 个 \(1\) 和 \(n-i\) 个 \(0\),则定义 \(0\) 的权值是 \(i\),\(1\) 的权值是 \(i-n\),一个字符串的权值是其所有字符权值之和。

所有长为 \(l\) 的循环子串的权值的平均值为 \(0\),且两个相同长度的字符串相差不超过 \(1\) 当且仅当权值相差不超过 \(n\),且相同长度的字符串权值\(\bmod n\) 的值都相同。

所以,一个字符串是好的当且仅当所有子串的权值 \(\in(-n,n)\),也就是说 \(\exist k\in[0,n)\) 使得所有前缀和 \(\in(k-n,k]\)。

从大到小枚举 \(k\),此时字符串是唯一的,维护出这个字符串然后顺便计算出不符合询问串限制的位置数量,如果为 \(0\) 说明满足条件。

时间复杂度 \(O(n^2)\),而且还可以得到答案也是 \(\le n^2\) 的。注意不要算重了。

#include<bits/stdc++.h>
#define PB emplace_back
using namespace std;
const int N = 1030;
int T, n, ans, pre[N], cnt; bool num[N]; char str[N];
vector<int> vec[N];
void solve(){
    scanf("%s", str+1); n = strlen(str+1); ans = 0;
    for(int i = 0;i <= n;++ i){
        cnt = pre[0] = 0; vec[0].PB(0);
        for(int j = 1;j <= n;++ j){
            if(pre[j-1] + i < n){
                pre[j] = pre[j-1] + i;
                num[j] = false;
                cnt += str[j] == '1';
            } else {
                pre[j] = pre[j-1] + i - n;
                num[j] = true;
                cnt += str[j] == '0';
            } if(pre[j] >= 0) vec[pre[j]].PB(j);
        } for(int j = n-1;;-- j){
            ans += !vec[j].empty() && !cnt;
            if(!j) break;
            for(int k : vec[j]){
                cnt += (str[k] == '0') + (str[k+1] == '1') - (str[k] == '1') - (str[k+1] == '0');
                swap(num[k], num[k+1]);
            }
        } for(int j = 0;j < n;++ j) vec[j].resize(0);
    } printf("%d\n", ans);
} int main(){scanf("%d", &T); while(T --) solve();}

Catcats Contest #2

己酸集合 (geo)

每个询问分别做,对左右边界跑 two-pointer。时间复杂度 \(O(nq)\)。

构造题 (buhui)

IOI2006 的加强版,有点东西(/kel

首先发现一个性质:如果凸包上 \(S,T\) 不是连续段,则一定无解。构造证明其他情况都有解。

主要思路是把凸包范围做一个三角剖分,然后每个三角形内部分别做。

不知为何考虑递归构造,设 \(\texttt{Solve(A,B,C)}\) 表示 \(A,B,C\) 是逆时针方向,其中 \(A\) 与 \(B,C\) 分别属于不同集合,\(BC\) 有连边,你需要将 \(\Delta ABC\) 内部的所有与 \(A\) 所属集合相同的点与 \(A\) 连通,与 \(B\) 所属集合相同的点与 \(B\) 或 \(C\) 中一个连通。

若内部点全都与 \(B,C\) 所属集合相同,就可以随便分治了,因为全部相同所以只要保证是一棵生成树即可。

否则任意选择一个与 \(A\) 所属集合相同的点 \(X\),与 \(A\) 连边,将该三角形划分为三个小三角形,变成了子问题 \(\texttt{Solve(X,B,C),Solve(C,A,X),Solve(B,X,A)}\),递归做。

至于三角剖分,分类讨论一下:

  • 若凸包上的点所属集合全相同,那么就在内部找一个不同的,以其为中心做三角剖分。
  • 若凸包上的点所属集合不同,则一定是一段 \(S\) 还有一段 \(T\),两边可以分别做,具体实现见代码(雾

在随机数据下的时间复杂度大概是 \(O(n\log n)\),最坏是 \(O(n^2)\)。

#include<bits/stdc++.h>
#define PB emplace_back
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int, int> pii;
const int N = 3003;
template<typename T>
void read(T &x){
    int ch = getchar(); x = 0;
    for(;ch < '0' || ch > '9';ch = getchar());
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
} int n, m;
struct Point {
    int x, y;
    Point(int _x = 0, int _y = 0): x(_x), y(_y){}
    bool operator < (const Point &o) const {if(x != o.x) return x < o.x; return y < o.y;}
    LL operator * (const Point &o) const {return (LL)x * o.y - (LL)y * o.x;}
    Point operator - (const Point &o) const {return Point(x - o.x, y - o.y);}
    Point operator + (const Point &o) const {return Point(x + o.x, y + o.y);}
} p[N];
vector<pii> ans[2];
inline bool col(int x){return x > n;}
void adde(int x, int y){
    assert(col(x) == col(y));
    if(col(x)) ans[1].PB(x - n, y - n);
    else ans[0].PB(x, y);
}
bool intri(int a, int b, int c, int d){ // d in angle bac ?
    Point v1 = p[b] - p[a], v2 = p[c] - p[a], v3 = p[d] - p[a];
    if(v1 * v2 < 0) swap(v1, v2);
    return v1 * v3 > 0 && v3 * v2 > 0;
}
void work(int a, int b, int c, VI now){
    // printf("a = %d, b = %d, c = %d\n", a, b, c);
    if(now.empty()) return; VI tmp;
    for(int i : now) if(col(i) == col(a)){
        adde(a, i); tmp.resize(0);
        for(int j : now) if(intri(i, a, b, j)) tmp.PB(j);
        work(b, i, a, tmp); tmp.resize(0);
        for(int j : now) if(intri(i, b, c, j)) tmp.PB(j);
        work(i, b, c, tmp); tmp.resize(0);
        for(int j : now) if(intri(i, c, a, j)) tmp.PB(j);
        work(c, a, i, tmp); return;
    } static LL dis[N];
    for(int i : now) dis[i] = labs((p[c] - p[b]) * (p[i] - p[b]));
    sort(now.begin(), now.end(), [&](int u, int v){return dis[u] < dis[v];});
    adde(b, now[0]); for(int i = 1;i < now.size();++ i) adde(now[i-1], now[i]);
}
int main(){
    read(n); read(m);
    for(int i = 1;i <= n+m;++ i){read(p[i].x); read(p[i].y);}
    VI id(n+m), hul; iota(id.begin(), id.end(), 1);
    sort(id.begin(), id.end(), [&](int a, int b){return p[a] < p[b];});
    for(int i : id){
        while(hul.size() > 1 && (p[i] - p[hul.back()]) * (p[i] - p[hul[hul.size()-2]]) > 0) hul.pop_back();
        hul.PB(i);
    } id.pop_back(); reverse(id.begin(), id.end()); int tmp = hul.size();
    for(int i : id){
        while(hul.size() > tmp && (p[i] - p[hul.back()]) * (p[i] - p[hul[hul.size()-2]]) > 0) hul.pop_back();
        hul.PB(i);
    } hul.pop_back();
    int pos = 1; for(;pos < hul.size() && col(hul[0]) == col(hul[pos]);++ pos);
    // printf("pos = %d\n", pos);
    if(pos == hul.size()){
        for(int i = 1;i < hul.size();++ i) adde(hul[i-1], hul[i]);
        hul.PB(hul[0]);
        for(int i = 1;i <= n+m;++ i) if(col(i) != col(hul[0])){
            for(int j = 1;j < hul.size();++ j){
                int a = hul[j-1], b = hul[j]; VI tmp;
                for(int k = 1;k <= n+m;++ k) if(intri(i, a, b, k)) tmp.PB(k);
                work(i, a, b, tmp);
            } break;
        }
    } else {
        rotate(hul.begin(), hul.begin() + pos, hul.end());
        // for(int i : hul) printf("%d ", i); putchar('\n');
        for(pos = 1;pos < hul.size() && col(hul[0]) == col(hul[pos]);++ pos);
        for(int _ = pos;_ < hul.size();++ _) if(col(hul[0]) == col(hul[_])){puts("Impossible"); return 0;}
        for(int i = 1;i < pos;++ i){
            int a = hul[pos], b = hul[i-1], c = hul[i]; VI tmp;
            for(int k = 1;k <= n+m;++ k) if(intri(a, b, c, k)) tmp.PB(k);
            adde(b, c); work(a, b, c, tmp);
        } for(int i = pos+1;i < hul.size();++ i){
            int a = hul[0], b = hul[i-1], c = hul[i]; VI tmp;
            for(int k = 1;k <= n+m;++ k) if(intri(a, b, c, k)) tmp.PB(k);
            adde(b, c); work(a, b, c, tmp);
        }
    } assert(ans[0].size() == n-1); assert(ans[1].size() == m-1);
    for(int _ = 0;_ < 2;++ _) for(pii &u : ans[_]) printf("%d %d\n", u.fi, u.se);
}

最长环 (route)

人类智慧,肝败吓疯(

#include<bits/stdc++.h>
using namespace std;
const int N = 303, B = 666, NIF = -1000000007;
template<typename T>
void read(T &x){
    int ch = getchar(); x = 0;
    for(;ch < '0' || ch > '9';ch = getchar());
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
} template<typename T>
bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
int n, m, k, x[N], y[N], z[N], a[N][N], d[2][N][N], w[N][N][3], ans; bool f[N], o;
void ins(int u, int v, int x){
    int p = 0;
    for(;p < 3;++ p){
        int t = w[u][v][p];
        if(a[u][t] + a[v][t] < a[u][x] + a[v][x]) break;
    } if(p < 3){
        for(int i = 2;i > p;-- i) w[u][v][i] = w[u][v][i-1];
        w[u][v][p] = x;
    }
}
void calc(int u, int v, int x, int y){
    int p = 0;
    while(w[u][v][p] == x || w[u][v][p] == y) ++ p;
    int z = w[u][v][p];
    chmax(d[o][x][y], a[x][u] + a[u][z] + a[z][v] + a[v][y]);
}
void solve(int k){
    for(int i = 0;i <= n;++ i)
        for(int j = 0;j <= n;++ j) d[o][i][j] = NIF;
    switch(k){
    case 1: for(int i = 1;i <= n;++ i) if(f[i] == o) d[o][i][i] = 0; break;
    case 2:
        for(int i = 1;i <= m;++ i) if(f[x[i]] == o && f[y[i]] == o)
            d[o][x[i]][y[i]] = d[o][y[i]][x[i]] = z[i]; break;
    case 3:
        for(int i = 1;i <= m;++ i) if(f[x[i]] == o && f[y[i]] == o)
            for(int j = 1;j <= m;++ j) if(i != j && f[x[j]] == o && f[y[j]] == o){
                if(x[i] == x[j]) chmax(d[o][y[i]][y[j]], z[i] + z[j]);
                else if(y[i] == x[j]) chmax(d[o][x[i]][y[j]], z[i] + z[j]);
                else if(x[i] == y[j]) chmax(d[o][y[i]][x[j]], z[i] + z[j]);
                else if(y[i] == y[j]) chmax(d[o][x[i]][x[j]], z[i] + z[j]);
            } break;
    case 4:
        for(int i = 1;i <= m;++ i) if(f[x[i]] == o && f[y[i]] == o)
            for(int j = 1;j <= m;++ j) if(i != j && f[x[j]] == o && f[y[j]] == o && x[i] != x[j] && x[i] != y[j] && y[i] != x[j] && y[i] != y[j]){
                chmax(d[o][x[i]][x[j]], z[i] + z[j] + a[y[i]][y[j]]);
                chmax(d[o][x[i]][y[j]], z[i] + z[j] + a[y[i]][x[j]]);
                chmax(d[o][y[i]][x[j]], z[i] + z[j] + a[x[i]][y[j]]);
                chmax(d[o][y[i]][y[j]], z[i] + z[j] + a[x[i]][x[j]]);
            } break;
    case 5:
        for(int i = 1;i <= n;++ i) if(f[i] == o)
            for(int j = 1;j <= n;++ j) if(f[j] == o){
                w[i][j][0] = w[i][j][1] = w[i][j][2] = 0;
            }
        for(int i = 1;i <= m;++ i) if(f[x[i]] == o && f[y[i]] == o)
            for(int j = 1;j <= m;++ j) if(i != j && f[x[j]] == o && f[y[j]] == o){
                if(x[i] == x[j]) ins(y[i], y[j], x[i]);
                else if(y[i] == x[j]) ins(x[i], y[j], y[i]);
                else if(x[i] == y[j]) ins(y[i], x[j], x[i]);
                else if(y[i] == y[j]) ins(x[i], x[j], y[i]);
            }
        for(int i = 1;i <= m;++ i) if(f[x[i]] == o && f[y[i]] == o)
            for(int j = 1;j <= m;++ j) if(i != j && f[x[j]] == o && f[y[j]] == o && x[i] != x[j] && x[i] != y[j] && y[i] != x[j] && y[i] != y[j]){
                calc(x[i], x[j], y[i], y[j]);
                calc(y[i], x[j], x[i], y[j]);
                calc(x[i], y[j], y[i], x[j]);
                calc(y[i], y[j], x[i], x[j]);
            }
    }
}
int main(){
    read(n); read(m); read(k);
    for(int i = 0;i <= n;++ i)
        for(int j = 0;j <= n;++ j) a[i][j] = NIF;
    for(int i = 1;i <= m;++ i){
        read(x[i]); read(y[i]); read(z[i]);
        a[x[i]][y[i]] = a[y[i]][x[i]] = z[i];
    } for(int _ = 0;_ < B;++ _){
        for(int i = 1;i <= n;++ i) f[i] = rng() & 1;
        if(k == 6){o = 0; solve(2); o = 1; solve(4);}
        else {o = 0; solve(k>>1); o = 1; solve(k+1>>1);}
        for(int i = 1;i <= m;++ i) if(f[x[i]] && !f[y[i]]) swap(x[i], y[i]);
        for(int i = 1;i <= m;++ i) if(f[x[i]] != f[y[i]])
            for(int j = 1;j <= m;++ j) if(i != j && f[x[j]] != f[y[j]])
                chmax(ans, d[0][x[i]][x[j]] + d[1][y[i]][y[j]] + z[i] + z[j]);
    } if(ans > 0) printf("%d\n", ans); else puts("impossible");
}

Catcats Contest #3

计数题 (count)

壬被玩傻了

首先 \(n,m\) 都为奇数时答案是 \(1\)。

其次 \(n\) 为偶数,\(m\) 为奇数时答案是 \((n/2+1)^{(m-1)/2}\),这事因为 \(1\) 所在列已经被确定,每个要填的列都有 \(n/2+1\) 种独立的方式。

然后就是 \(n,m\) 都为偶数的情况。若将整个矩阵划分为 \(2\times 2\) 的 block,则每块都恰有一个 \(1\),且每行、每列填的 \(1\) 的位置形如 \(010101|101010\),我们按列做 dp,将这个分界线的位置记录下来,并且将第 \(i\) 个 block 内的 \(1\) 是否靠右记录下来。要做的就是避免下面这种情况:

.x..	..x.
...x	x...
x...	...x
..x.	.x..

转移是一个 SOS 形式,时间复杂度 \(O(n^3m2^n)\),其中这里的 \(n,m\) 是原来的一半。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n, m, lim, mod, dp[2][13][4096], a[4096], ans; bool cr;
int ksm(int a, int b){
    int res = 1;
    for(;b;b >>= 1, a = (LL)a * a % mod)
        if(b & 1) res = (LL)res * a % mod;
    return res;
} void qmo(int &x){x += x >> 31 & mod;}
void FMT(int *a){
    for(int mid = 1;mid < lim;mid <<= 1)
        for(int i = 0;i < lim;i += mid<<1)
            for(int j = i;j < i+mid;++ j)
                qmo(a[j] += a[j|mid] - mod);
}
int main(){
    scanf("%d%d%d", &n, &m, &mod);
    if((n & 1) && (m & 1)){puts("1"); return 0;}
    if(n & 1) swap(n, m); if(m & 1){printf("%d\n", ksm((n>>1)+1, m>>1)); return 0;}
    n>>=1; m>>=1; lim = 1<<n;
    for(int i = 0;i <= n;++ i)
        for(int j = 0;j < lim;++ j)
            dp[0][i][j] = 1;
    for(int _ = 1;_ < m;++ _, cr ^= 1){
        memset(dp[!cr], 0, sizeof dp[!cr]);
        for(int i = 0;i <= n;++ i){
            memcpy(a, dp[cr][i], lim<<2); FMT(a);
            for(int j = 0;j <= n;++ j)
                for(int S = 0;S < lim;++ S){
                    int T = S;
                    for(int k = i+1;k < j;++ k)
                        if((S>>k-1&1) && !(S>>k&1)) T |= 1<<k;
                    for(int k = j;k < i-1;++ k)
                        if(!(S>>k&1) && (S>>k+1&1)) T |= 1<<k;
                    qmo(dp[!cr][j][S] += a[T] - mod);
                }
        }
    }
    for(int i = 0;i <= n;++ i)
        for(int j = 0;j < lim;++ j)
            qmo(ans += dp[cr][i][j] - mod);
    printf("%d\n", ans);
}

Catcats Contest #4

平方过百万 (million)

有一个我不会的转化:对于第 \(2\) 种操作,在 \((u_p,v_p)\) 之间加一条权值为操作编号的边,则编号为 \(T\) 的询问 \(\texttt{1 u}\) 即为求从 \(u\) 开始走权值递增且 \(\le T\) 的边,能到达的节点个数。

可以使用点分治,假设当前处理的分治中心是 \(r\),对每个操作 \(2\),求出从它到达分治中心的最早时间,和分治中心能到达它所出发的最晚时间。这大概是一个二维偏序,用排序 + 树状数组即可,时间复杂度 \(O((n+m)\log n)\)。有点烦,自闭了

Catcats Contest #5

构造题 (buhui)

stm 域论,等拿到数学女孩第 5 册了就学(

有限域相关基础:\(\text{GF}(p^n)\) 可以表示为系数模 \(p\),整体模 \(n\) 次本原多项式 \(F\) 的意义下定义四则运算,由不超过 \(n-1\) 次的多项式构成的代数结构。

可以证明这东西是域,也可以证明它存在且唯一。其特征为 \(p\),所以 \((x+y)^p=x^p+y^p\)。

那至于这题怎么做:取最小的 \(w\) 使得 \(2^w>|S|\),计算出 \(\text{GF}(2^w)\),构造 \(a_i=i\cdot 2^w+f(i)\),其中 \(f(i)\) 是 \(\text{GF}(2^w)\) 意义下的立方。

至于正确性,考虑反证法:假设存在 \((i_1,j_1)\ne (i_2,j_2)\) 使得 \(a_{i_1}\oplus a_{j_1}=a_{i_2}\oplus a_{j_2}\),则 \(i_1,j_1,i_2,j_2\) 必须两两不同,且在 \(\text{GF}(2^w)\) 意义下 \(i_1+j_1=i_2+j_2\ne 0\),且 \(i_1^3+j_1^3=i_2^3+j_2^3\),得到 \(i_1^2+i_1j_1+j_1^2=i_2^2+i_2j_2+j_2^2\),所以 \(i_1j_1=i_2j_2\)。

令 \(s=i_1+j_1,p=i_1j_1\),得到 \(x(s-x)=p\) 有至少 \(4\) 个根,而其只有 \(2\) 次,矛盾。

因此 \(a_i\oplus a_j\) 两两不同,得证。本原多项式可以事先预处理,具体实现上 mod 2 运算可以使用异或运算。

这个构造的背景在于一个经典问题:如何在 \([0,p)^2\) 中构造 \(p\) 个整点使得不存在三点共线,其中 \(p\) 是质数。一种构造是取 \(x_i=i,y_i=i^2\bmod p\),可以得到 \(\frac{y_j-y_i}{x_j-x_i}=\frac{y_k-y_j}{x_k-x_j}\Rightarrow x_i+x_j=x_j+x_k\Rightarrow x_i=x_k\)。

#include<bits/stdc++.h>
using namespace std;
const int _[] = {2,7,11,19,37,67,131,283,515,1033,2053,4105};
int n, mod, lim;
int red(int x){
    int p = mod; while(p <= x) p <<= 1;
    for(;p >= mod;p >>= 1) if((x ^ p) < x) x ^= p;
    return x;
} int mul(int x, int y){
    int r = 0;
    for(;x;x >>= 1, y <<= 1) if(x & 1) r ^= y;
    return red(r);
}
int main(){
    scanf("%d", &n); mod = _[n-1]; lim = 1<<n;
    for(int i = 0;i < lim;++ i) printf("%d ", i<<n|mul(mul(i,i),i));
}


这篇关于Catcats Contest 系列的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程