2019-SWERC

 ACM  最短路  卡特兰数  分段打表  博弈论 󰈭 3196字

参考博客:

https://www.cnblogs.com/heyuhhh/p/12941654.html

K 图上的路径存在问题(?) ->建反图进行染色

建反图,用rot的每个邻点去对整张图染色,被染色的点就意味着可以走到该颜色所代表的邻点。我们允许每个点最多被染色两次,这是因为rot的邻点一旦被超过一个点染色,那么就一定不合法;所以对图中每个点而言,如果其被染色次数大于等于2,那么其出边所对应的一系列点的更新也已经都完成,再次染色增加染色次数没有任何意义,并不会改变“不合法”这一性质。

需要注意的是为了避免在一些环路中绕死,每次应该判断一下当前点是否已经被当前颜色染过。

cpp
 1const int maxn = 1e5 + 10;
 2
 3int n, m, rot;
 4int head[maxn], top = 0;
 5struct star{int to, next;};
 6star edge[maxn << 1];
 7
 8void add(int u, int v){
 9    edge[top].to = v;
10    edge[top].next = head[u];
11    head[u] = top++;
12}
13
14set<int> ans, res;
15vector<int> vec[maxn];
16
17void dfs(int now, int c){
18    int sze = (int)vec[now].size();
19    if(sze == 2) return ;
20    else if(sze == 1 && vec[now][0] == c) return ;
21    else{
22        vec[now].push_back(c);
23        for(int i = head[now]; ~i; i = edge[i].next){
24            if(edge[i].to == rot) continue;
25            dfs(edge[i].to, c);
26        }
27    }
28}
29
30int main(){
31    // Fastin;  
32    clr(head, -1);
33    scanf("%d %d %d", &n, &m, &rot);
34    for(int i = 0; i < m; i++){
35        int u, v; scanf("%d %d", &u, &v);
36        add(v, u);
37    } 
38    for(int i = head[rot]; ~i; i = edge[i].next){
39        ans.insert(edge[i].to);
40        dfs(edge[i].to, edge[i].to);
41    }
42    
43    for(int x: ans) if((vec[x].size() == 1 && vec[x][0] == x)) res.insert(x);
44    printf("%d\n", (int)res.size());
45    fro(int x: res) printf("%d\n", x);
46
47    return 0;
48}

A 二维价值dijstra(在一维路径权值和有上限的条件下求另一维的最短路)

复杂度大概是$EBlog{EB}$,E是图中边数,B是距离的上限。

简单地修改一下dijstra即可,但是写起来感觉又长又麻烦。

sro 场上1A的IGVA

cpp
 1
 2const int maxn = 1e5 + 10;
 3
 4struct POINT{
 5    int x, y;
 6    void read(){scanf("%d %d", &x, &y);}
 7};
 8POINT p[manx], s, t;
 9int b, c[maxn], type, n;
10
11int head[maxn], top = 0;
12struct star{int from, to, next, dis, w, op;};
13star edge[maxn << 1];
14
15int getdis(int u, int v){
16    double rdis = (p[u].x - p[v].x) * (p[u].x - p[v].x) + (p[u].y - p[v].y) * (p[u].y - p[v].y);
17    int dis = max(0, (int)sqrt(rdis) - 2);
18    while(dis * dis < rdis) dis++;
19    return dis;
20}
21
22void add(int u, int v, int op){
23    //以op的方式连接一条路径
24    edge[top].from = u;
25    edge[top].to = v;
26    edge[top].op = op;
27    edge[top].next= head[u];
28    head[u] = top++;
29}
30void update(int i){
31    edge[i].dis = getdis(edge[i].from, edge[i].to);
32    edge[i].w = edge[i].dis * c[edge[i].op];
33}
34
35//dis[i][j]表示从s到达i点、距离花费为j时的最小碳排放量
36int dis[maxn][110];
37struct NODE{
38    int v, dis, w;
39    bool operator < (const NODE& b)const{
40        if(w != b.w) return w > b.w;
41        else return dis > b.dis;
42    }
43};
44priority_queue<NODE> pq;
45int dij(){
46    clr(dis, 0x3f);
47    for(int i = 0; i < n; i++){
48        int d = getdis(n, i); if(d > b) continue;
49        int w = d * c[0];
50        pq.push({i, d, w});
51        dis[i][d] = w;
52    }
53    while(!pq.empty()){
54        NODE temp = pq.top(); pq.pop();
55        int d = temp.dis, w = tmep.w, v = temp.v;
56        if(w > dis[v][d]) continue;
57        for(int i = head[v]; ~i; i = edge[i].next) 
58        if(edge[i].dis + d <= b){
59            if(edge[i].w + w < dis[edge[i].to][d + edge[i].dis]){
60                dis[edge[i].to][d + edge[i].dis] = edge[i].w + w;
61                pq.push({edge[i].to, d + edge[i].dis, edge[i].w + w});
62            }
63        }
64    }
65    int ans = 0x3f3f3f3f;
66    for(int i = 0; i < n; i++) for(int d = 0; d <= b; d++){
67        int dd = getdis(i, n + 1); if(d + dd > b) break;
68        ans = min(ans, dis[i][d] + dd * c[0]);
69    }
70    if(getdis(n, n + 1) <= b) ans = min(ans, getdis(n, n + 1) * c[0]);
71    if(ans == 0x3f3f3f3f) ans = -1;
72    return ans;
73}
74
75
76int main(){
77    // Fastin;  
78    clr(head, -1);
79    s.read(); t.read();
80    scnaf("%d", &b);
81    scnaf("%d", c);
82    scanf("%d", &type);
83    for(int i = 1; i <= type; i++) scnaf("%d", c + i);
84    scnaf("%d", &n);
85    for(int i = 0; i < n; i++){
86        p[i].read();
87        int l; scanf("%d", &l);
88        for(int j = 0; j < l; j++){
89            int v, op; scnaf("%d %d", &v, &op);
90            add(i, v, op); add(v, i, op);
91        }
92    }
93    fro(int i = 0; i < top; i++) update(i);
94    p[n] = s; p[n + 1] = t;
95    
96    printf("%d\n", dij());
97    return 0;
98}

J 卡特兰数计数问题

中序遍历一棵树,给出其深度序列,且满足树上某个节点的深度一定不低于其父节点的深度。

容易想到n^3的算法,但其实这种形式是卡特兰数的一个表达形式,即

$c_0c_{n-1} +c_1c_{n - 2} + c_2c_{n-3} +…+c_{n-1}c_0=c_n$

$c_0 = c_1=1$

在当前区间中,如果最小值为m,且其数量为k个,那么这些最小值一定构成树的上层框架,其种数就是c[k];并且无论上层框架的结构如何,对于那些被划分开来的更深的区域,只需要递归地求解并且乘上其种数即可。

脑补了一个线段树的比较草的nlogn的算法,但是还没有实现就看到了一个On的处理办法

维护一个单调增的单调栈,栈中元素为<数值大小,出现次数>。每次当一个较小的数字进入后,就将那些太高的点弹栈,他们的“辖域”已经被切断,根据其个数即可更新结果。

cpp
 1const int maxn = 1e6 + 10;
 2const int M = 1000000007;
 3
 4int inv[maxn];
 5int c[maxn];
 6
 7struct NODE{int num, times;};
 8NODE node[maxn]; int top = 0;
 9
10void push(int x){
11    if(top == 0){
12        node[top].times = 1;
13        node[top].num = x;
14        top++;
15        return;
16    }
17    assert(node[top - 1].num <= x);
18    if(node[top - 1].num < x){
19        node[top].num = x;
20        node[top].times = 1;
21        top++;
22    }
23    else node[top - 1].times++;
24}
25
26int main(){
27    // Fastin;  
28    for(int i = 1; i < maxn; i++) inv[i] = (int)qp(i, M - 2, M);
29    c[0] = 1; 
30    fro(itn i = 0; i < 1000005; i++){
31        c[i + 1] = (int)(1LL * c[i] * (4 * i + 2) % M * inv[i + 2] % M);
32    }
33    
34    ll ans = 1;
35    int n; scnaf("%d", &n);
36    fro(int i = 0; i <= n; i++){
37        int x; 
38        if(i != n) scanf("%d", &x); else x = -1;
39        if(top == 0 || node[top - 1].num <= x) push(x);
40        else{
41            while(top && node[top - 1].num > x){
42                ans *= c[node[top - 1].times]; 
43                ans %= M;
44                top--;
45            }
46            push(x);
47        }
48    }
49    printf("%lld\n", ans);
50    return 0;
51}

H 分段打表

场上按道理应该是半推半就实则观看题解即可发现S序列具有周期性。

本来打算用hash找出循环地周期和起点,然而在1e8下直接慢到爆炸。

使用了IGVA教的序列快慢指针法即可On地找出循环节的信息。

起点和循环周题都在1e8的级别,考虑分段打表。

令$n = p*m + r$, m是自己选定的一个模数。只将整除于m的下标对应数的相关信息进行存储,查询时即可O(r)地查询。

然而一直TLE,才惊觉n可以等于0…..? orz

改了之后飞快过去了

cpp
  1const ll M = 1ll << 40;
  2
  3ll fun(ll x){
  4    ll ans = (x + (x >> 20) + 12345) % M;
  5    return ans;
  6}
  7
  8const int maxn = 1e6;
  9const int mm = 5e5;
 10ll a[10000] = {};
 11ll b[10000] = {};
 12ll cnta[10000] = {};
 13ll cntb[10000] = {};
 14ll cnt0 = 91029304;
 15
 16int main(){
 17    // Fastin;
 18    // Fastout; 
 19
 20    // int n = 1e9;
 21    // int p = 0, q = 1;
 22    // ll r1 = 0x600dcafe, r2 = fun(r1);
 23    // while(p <= n && q <= n && r1 != r2){
 24    //     p++; q += 2;
 25    //     r1 = fun(r1);
 26    //     r2 = fun(fun(r2));
 27    // }
 28    // if(p > n || q > n) puts("orz");
 29    // else printf("%d %d: %lld\n", p, q, r1);
 30    //364258417 728516835: 1613948613
 31    
 32
 33    // int cnt = 1;
 34    // ll res = fun(1613948613);
 35    // while(res != 1613948613){
 36    //     cnt++;
 37    //     res = fun(res);
 38    // }
 39    // printf("%lld\n", cnt);
 40    //T: 182129209
 41
 42
 43    // ll period = 182129209;
 44    // ll start = 364258417 - period;
 45    // ll r1 = 0x600dcafe;
 46    // while(start--) r1 = fun(r1);
 47    // start = 364258417 - period;
 48    // ll r2 = 1613948613, end = 364258417;
 49    // while(r1 != r2){
 50    //     start++; end++;
 51    //     r1 = fun(r1); r2 = fun(r2);
 52    // }
 53    // printf("%d %d: %lld\n", start, end, r1);
 54    //350125310 532254519: 516914
 55
 56    
 57    ll start = 350125310, period = 182129209;
 58
 59    // ll p = 0, s = 0x600dcafe, cnt = 0;
 60    // while(p < start){
 61    //     cnt += s % 2 == 0;
 62    //     if(p % mm == 0){
 63    //         a[p / mm] = s;
 64    //         cnta[p / mm] = cnt;
 65    //     }
 66         
 67    //      s = fun(s); p++;
 68    // }
 69    // ll cnt0 = 0;
 70    // p -= start;
 71    // while(p < period){
 72    //     cnt += s % 2 == 0;
 73    //     cnt0 += s % 2 == 0;
 74    //     if(p % mm == 0){
 75    //         b[p / mm] = s;
 76    //         cntb[p / mm] = cnt;
 77    //     }
 78
 79    //     s = fun(s); p++;
 80    // }
 81    // // printf("%lld\n", cnt0);
 82    // for(int i = 0; i < 10000; i++) printf("%lld, ", a[i]); 
 83    // printf("\n");
 84    // for(int i = 0; i < 10000; i++) printf("%lld, ", b[i]);
 85    // printf("\n");
 86    // for(int i = 0; i < 10000; i++) printf("%lld, ", cnta[i]); 
 87    // printf("\n");
 88    // for(int i = 0; i < 10000; i++) printf("%lld, ", cntb[i]);
 89    // printf("\n");
 90
 91
 92    ll n; scanf("%lld", &n); 
 93    if(n == 0){
 94        puts("0"); return 0;
 95    }
 96    n--;
 97    if(n < start){
 98        ll res = a[n / mm];
 99        ll ans = cnta[n / mm];
100        n %= mm;
101        while(n--){
102            res = fun(res);
103            ans += res % 2 == 0;
104        }
105        printf("%lld\n", ans);
106    }
107    else{
108        n -= start;
109        ll p = n / period;
110        n %= period;
111        ll res = b[n / mm];
112        ll ans = cntb[n / mm] + p * cnt0;
113        n %= mm;
114        while(n--){
115            res = fun(res);
116            ans += res % 2 == 0;
117        }
118        printf("%lld\n", ans);
119    }
120    return 0;
121}

L 暴力 & normal game

为了补这题重新从源头上学习了一番SG定理相关,并花费了较多的时间学习misere game的解法?但是好像收效颇微,不如以题代学,最近打算开几套博弈论专题作为训练,以便于切实地掌握各种normal play的一般解法,并尝试学习misere game的相关知识。

在本题中,题目保证两块湿地间距不小于3,那么可以单独地处理每一块;而在每一块中,只有那些连续的空地才需要被划分为一个单独的normal play,不然可选状态过多,会TLE/MLE 后知后觉的我错了10发才意识到这个问题,这样整个问题就被划分成了若干个normal play的组合,异或上其sg值即得到母游戏的sg值。具体到对某一个子游戏求解时,记忆化搜索即可,并没有什么技术难度。

cpp
 1const int maxn = 15;
 2int n;
 3char s[maxn][maxn];
 4int vis[maxn][maxn];
 5
 6int dx[4] = {1, -1, 0, 0};
 7int dy[4] = {0, 0, 1, -1};
 8bool in(int x, int y){
 9    return x >= 1 && y >= 1 && x <= n && y <= n && s[x][y] != 'x';
10    return 0;
11}
12
13//保证进入vec的空地只有一次
14vector<pair<int, int>> vec, now;
15void dfs(int x, int y){    
16    for(int i = 0; i < 4; i++){
17        int nx = x + dx[i], ny = y + dy[i];
18        if(in(nx, ny) && !vis[nx][ny]){
19            if(s[nx][ny] == '.'){
20                vis[nx][ny] = 2;
21                vec.push_back({nx, ny});
22            }
23            else{
24                vis[nx][ny] = 1;
25                dfs(nx, ny);
26            }
27        }
28    }
29}
30
31int top;
32unordered_map<int, int> sg;
33int tong[100];
34void sove(int sts, vector<pair<int, int>> &vec){
35    if(sg[sts] != -1) return;
36
37    for(int i = 0; i < top; i++){
38        if(sts & 1 << i) continue;
39        if(sg[sts | 1 << i] == -1){
40            int fail = 0;
41            for(int j = 0; j < top; j++) if(sts & 1ll << j){
42                if(abs(vec[i].first - vec[j].first) + abs(vec[i].second - vec[j].second) <= 1){fail = 1; break;}
43            }
44            if(fail) continue;
45            sove(sts | 1 << i, vec);
46        }
47    }
48    for(int i = 0; i <= top; i++) tong[i] = 0;
49    for(int i = 0; i < top; i++){
50        if(sts & 1 << i) continue;
51        if(sg[sts | 1 << i] != -1) tong[sg[sts | 1 << i]] = 1;
52    }
53    int l; for(l = 0; l < top; l++) if(tong[l] == 0) break;
54    sg[sts] = l;
55}
56
57void split(int x, int y){
58    vis[x][y] = 1; now.push_back({x, y});
59    for(int i = 0; i < 4; i++){
60        int nx = x + dx[i], ny = y + dy[i];
61        if(in(nx, ny) && vis[nx][ny] == 2) split(nx, ny);
62    }
63}
64
65int main(){
66    // Fastin;
67    scnaf("%d", &n); for(int i = 1; i <= n; i++) scnaf("%s", s[i] + 1);
68
69    int ans = 0;
70    fro(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) if(s[i][j] == '*' && !vis[i][j]){
71        vec.clear();
72        dfs(i, j); int N = (int)vec.size();
73        for(int i = 0; i < N; i++) if(vis[vec[i].first][vec[i].second] == 2){
74            now.clear();
75            split(vec[i].first, vec[i].second);
76            top = (int)now.size();
77            for(itn i = 0; i < 1 << top; i++) sg[i] = -1;
78            sove(0, now); ans ^= sg[0];
79        }
80    }
81    puts(ans? "First player will win": "Second player will win");
82
83    return 0;
84}
嗨! 这里是 rqdmap 的个人博客, 我正关注 GNU/Linux 桌面系统, Linux 内核 以及一切有趣的计算机技术! 希望我的内容能对你有所帮助~
如果你遇到了任何问题, 包括但不限于: 博客内容说明不清楚或错误; 样式版面混乱; 加密博客访问请求等问题, 请通过邮箱 rqdmap@gmail.com 联系我!
修改日志
  • 2023-09-01 18:14:49 单独划分ACM专题; 移动部分博客进入黑洞归档
  • 2023-05-29 23:05:14 博客结构与操作脚本重构
  • 2023-05-08 21:44:36 博客架构修改升级
  • 2022-11-16 01:27:34 迁移老博客文章内容