目录

无聊的做题记录

数学公式

题目

题解

错排公式

Dn = floor(n!/e + 0.5) = (n - 1) * (Dn-1 + Dn-2)

D1 = 0 , D2 = 1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//457ms/1000ms  488k/32768k
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
const ll mod = 998244353;
int main() {
    ll n;
    cin >> n;
    ll one = 0, to = 1;
    ll ans = 0;
    for(int i = 2; i < n; ++i){
        ans = i * (one + to);
        ans %= mod;
        one = to;
        to = ans;
    }
    cout << ans ;
    return 0;
}

===============================================================

贪心

题目

题解

最优策略一定是捕鱼和炖鱼并行,但是捕鱼的过程不能中断,所以就有时间浪费,分两种,1.在炖鱼结束前停止捕鱼,2.炖鱼结束前,捕一条鱼,炖鱼结束,捕鱼还没结束

对每一次炖鱼选择哪一种?2优于1在于同样是浪费时间,2可以多收获一条生鱼

所以每次都选择2策略,捕鱼顺序会影响时间,为了让浪费时间尽可能少,应该求出每一次炖鱼的“浪费时间”,然后从小到大排序,选择前“浪费时间的次数”个

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//436ms/1000ms 4024kb/65536kb
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
ll a[100005];
vector<ll> b;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _;
    cin >> _;
    while (_--) {
        b.clear();
        int n, k;
        cin >> n >> k;
        ll ans = 0;
        ll sum = 0;
        for(int i = 0; i < n; ++i) {
            cin >> a[i];
            ans += a[i];
            sum += a[i] / k;
            b.push_back(k - a[i] % k);
        }
        sort(b.begin(), b.end());
        for(int i = 0; i < n - sum - 1; ++i){
            ans += b[i];
        }
        ans += k;
        cout << ans << endl;
    }
    return 0;
}

===============================================================

树形dp dfs 数论

题目

题解

dp[i][j] 表示以 i 为根的树,i节点为j时的方案数

dp[i][j] = 每个子节点满足条件的方案数相乘(满足的条件是gcd(i,j) != w)

子节点又要求子节点所以形成dfs

由于求j满足gcd(i,j) = w比较好求(枚举w的倍数),所以满足条件的方案数是所有方案数减不满足条件方案数

所有方案数即dp[i][1] + dp[i][2] + … + dp[i][m] 这可以每次求完所有的dp[i][x] 之后求和,用sum[i]维护

整体过程就是先dfs子节点,处理完毕后遍历m,然后遍历每个子节点,算出该子节点对dp[i][j]的贡献(满足的方案数),最后求积,赋值给dp[i][j],然后再遍历一遍m在线预处理sum[i]

时间复杂度 O(m^2logm)

如果m是1e5,可能需要莫比乌斯反演

注意这不是一棵树,是无向图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
    //537ms/1000ms 8192kb/262144kb
    #include "bits/stdc++.h"
    using namespace std;
    using ll = long long;
    const int maxn = 1005;
    ll sum[maxn];
    const ll mod = 1e9 + 7;
    ll n, m;
    ll dp[maxn][maxn];
    struct edge{
        int to , next;
        ll w;
    }e[2*maxn];
    int tot,head[maxn];
    ll gcd(ll a, ll b){
        return b ? gcd(b, a%b) : a;
    }
    void add_edge(int u, int v, ll w){
        e[tot].to = v;
        e[tot].w = w;
        e[tot].next = head[u];
        head[u] = tot++;
    }
    void dfs(int x, int p){
        for(int i = head[x]; ~i; i = e[i].next){
            int v = e[i].to;
            if(v != p) dfs(v, x);
        }
        for(ll i = 1; i <= m; ++i){
            ll cnt = 1;
            for(int j = head[x]; ~j; j = e[j].next){
                int v = e[j].to;
                ll w = e[j].w;
                if(v == p) continue;
                ll rem = sum[v];
                for(ll k = w; k <= m; k += w){
                    if(gcd(i, k) == w){
                        rem -= dp[v][k];
                        rem += mod;
                        rem %= mod;
                    }
                }
                cnt *= rem;
                cnt %= mod;
            }
            dp[x][i] = cnt;
            dp[x][i] %= mod;
        }
        for(ll i = 1; i <= m; ++i){
            sum[x] += dp[x][i];
            sum[x] %= mod;
        }
    }

    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        cout.tie(nullptr);
        memset(head , -1 , sizeof(head));
        cin >> n >> m;
        for(int i = 0; i < n - 1; ++i){
            int u, v, w;
            cin >> u >> v >> w;
            add_edge(u, v, w);
            add_edge(v, u, w);
        }
        dfs(1, 0);
        cout << sum[1] << endl;
        return 0;
    }

===============================================================

dp 乘法原理

题目

题解

用dp处理排列组合问题,对于子序列的数量,考虑对前缀的依赖

dp[3] 表示扫描到当前abc的数量,dp[2]表示扫描到当前ab的数量,dp[1]表示扫描到当前a的数量,dp[0]表示扫描到当前单纯排列组合的数量

转移方程 dp[3] = dp[3] + dp[2] [ch[i] == 'c']

dp[2] = dp[2] + dp[1] [ch[i] == 'b']

dp[1] = dp[1] + dp[0] [ch[i] == 'a']

当前字符是?时,它有三种情况a, b, c所以对于dp[0], dp[1], dp[2], dp[3]都乘以3

然后再分a, b, c三种情况

对于c,dp[3] += dp[2] (这是未扫描到当前的dp[2])其他同理

初始化 dp[0] = 1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//61ms/1000ms 208kb/256mb
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod = 1e9 + 7;
char a[200005];
ll dp[4];
int main(){
    int n;
    cin >> n;
    cin >> a;
    dp[0] = 1;
    for(int i = 0; i < n; ++i){
        if(a[i] == 'a'){
            dp[1] = (dp[1] + dp[0]) % mod;
        }else if(a[i] == 'b'){
            dp[2] = (dp[2] + dp[1]) % mod;
        }else if(a[i] == 'c'){
            dp[3] = (dp[3] + dp[2]) % mod;
        }else{
            dp[3] = (3ll * dp[3] + dp[2]) % mod;
            dp[2] = (3ll * dp[2] + dp[1]) % mod;
            dp[1] = (3ll * dp[1] + dp[0]) % mod;
            dp[0] = (3ll * dp[0]) % mod;
        }
    }
    cout << dp[3] << endl;
    return 0;
}

===============================================================

dp bf 预处理

题目

题意

给定一个大写的字符串,每个大写的字母都代表三个字母的组合(qwe),每次释放一个qwe中的技能就会获得这个字母,至多只能获得三个,如果在已有三个字母的情况下获得释放技能,第一个字母将会被挤掉(类似于队列)当集齐该大写字母的三个字母组合时,释放r技能就能成功点亮这个大写字母,这时候字母队列中不会有任何变化,问最少需要释放几个技能把所有的大写字母按顺序点亮

题解

按顺序暴力,由于大写字母对应的是技能的组合,总共有6种,所以求出前6种组合对后6种组合的影响,然后对每个都取最小值,这样就形成dp

dp[i][j] 表示扫描完第 i 个大写字母时,对应的第 j 种组合(至多6种)的答案

初始化 dp[0][i] = 3 [0 <= i < 6]

状态转移方程 dp[i][j] = min(dp[i][j], dp[i - 1][k] + cot(string s[i - 1][k], string s[i][j])

最后的答案就是 min(dp[s.len - 1][i]) [0 <= i < 6]

如果不做优化会超时,优化的方法就是两处预处理(在代码中标注)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//358ms/1000ms 2772kb/1024mb
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
const int maxn = 100005;
//string s;
char s[maxn];
unordered_map<char, int> mp;
//括号不匹配
char ch[11][7][4] =
        {{"QQQ", "QQQ", "QQQ", "QQQ", "QQQ", "QQQ"}},
        {"QQW", "QWQ", "QQW", "QWQ", "WQQ", "WQQ"},
         {"QQE", "QEQ", "QQE", "QEQ", "EQQ", "EQQ"},
         {"WWW", "WWW", "WWW", "WWW", "WWW", "WWW"},
         {"QWW", "QWW", "WQW", "WWQ", "WQW", "WWQ"},
         {"WWE", "WEW", "WWE", "WEW", "EWW", "EWW"},
         {"EEE", "EEE", "EEE", "EEE", "EEE", "EEE"},
         {"QEE", "QEE", "EQE", "EEQ", "EQE", "EEQ"},
         {"WEE", "WEE", "EWE", "EEW", "EWE", "EEW"},
         {{"QWE", "QEW", "WQE", "WEQ", "EQW", "EWQ"}};

int dp[maxn][6];
int did(string s1, string s2){
    if(s1 == s2) return 0;
    if(s1[1] == s2[0] and s1[2] == s2[1]) return 1;
    if(s1[2] == s2[0]) return 2;
    return 3;
}
const int inf = 0x3f3f3f3f;
//int get_id(char x){
//    if(x == 'Y') return 0;
//    if(x == 'V') return 1;
//    if(x == 'G') return 2;
//    if(x == 'C') return 3;
//    if(x == 'X') return 4;
//    if(x == 'Z') return 5;
//    if(x == 'T') return 6;
//    if(x == 'F') return 7;
//    if(x == 'D') return 8;
//    return 9;
//}
int cot[11][7][11][7];
int main() {
    mp['Y'] = 0;
    mp['V'] = 1;
    mp['G'] = 2;
    mp['C'] = 3;
    mp['X'] = 4;
    mp['Z'] = 5;
    mp['T'] = 6;
    mp['F'] = 7;
    mp['D'] = 8;
    mp['B'] = 9;
    for(int i = 0; i < 10; ++i){
        for(int j = 0; j < 6; ++j){
            for(int k = 0; k < 10; ++k){
                for(int m = 0; m < 6; ++m){
                    string s1 = ch[i][j];
                    string s2 = ch[k][m];
                    cot[i][j][k][m] = did(s1, s2);
                }
            }
        }
    }
    //cin >> s;
    scanf("%s", s);
    int len = strlen(s);
    for(int i = 0; i <= len; ++i)
        for(int j = 0; j < 6; ++j)
            dp[i][j] = inf;
    //cout << len << endl;
    for(int i = 0; i < 6; ++i){
        dp[0][i] = 3;
    }
    for(int i = 1; i < len; ++i){
        int id1 = mp[s[i - 1]];   //在外层循环得到
        int id2 = mp[s[i]];
        for(int j = 0; j < 6; ++j){
            for(int k = 0; k < 6; ++k){
                dp[i][j] = min(dp[i][j], dp[i - 1][k] + cot[id1][k][id2][j]);  //!!!此处预处理
                //dp[i][j] = min(dp[i][j], dp[i - 1][k] + did(ch[id1][k], ch[id2][j]));
            }
        }
    }
    int ans = 0x3f3f3f3f;
    for(int i = 0; i < 6; ++i){
        ans = min(ans, dp[len - 1][i]);
    }
    cout << ans + len << endl;
    return 0;
}

===============================================================

dfs 图论 乘法原理

题目

题意

给一个森林,每个连通块都是仙人掌图,每次删除一些边,使得它变成一个森林且每个连通块都是一棵树,问有多少种删边方法

题解

根据仙人掌图的定义,对于每个环,至少删除一条边就能满足条件,所以计算每个环的边数

对于不在环上的边,可以任意删除或保留,假设每个环的边数为ci,不在环上的边数是r

$$ ans = 2^r*\prod (2^{c_i} - 1) $$

对于找环的边数,可以用tarjan算法的思想,用dfs序来判断是否遍历过这个点,如果遍历过就说明形成环,dfs序相减就是环的长度

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//358ms/1000ms 138640kb/1048576kb
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
const int maxv = 300005;
const ll mod = 998244353;
const int maxn = 2000005;
struct edge{
    int to,  nxt;
}e[maxn];
int tot,head[maxn];
void add_edge(int u,int v){
    e[tot].to = v;
    e[tot].nxt = head[u];
    head[u] = tot++;
}
vector<ll> res;
int dfn[maxv],low[maxv];
bool in_stack[maxv];
stack<int> s;
int tim;
ll qpow(ll x,ll n){
    ll res =1;
    while(n>0){
        if(n&1) res=res*x%mod;
        x=x*x%mod;
        n>>=1;
    }
    return res;
}
void tarjan_dfs(int x, int p){
    for(int i = head[x]; ~i; i = e[i].nxt){
        int v = e[i].to;
        if(v == p) continue;
        if(dfn[v] != 0){
            res.push_back(dfn[x] + 1 - dfn[v]);
        }else{
            dfn[v] = dfn[x] + 1;
            tarjan_dfs(v, x);
        }
    }
}
int main() {
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF){
        //reset
        res.clear();
        tim = 0;
        memset(dfn, 0, sizeof(dfn));
        memset(low, 0, sizeof(low));
        memset(in_stack, 0, sizeof(in_stack));
        while(!s.empty()) s.pop();
        tot = 0;
        memset(head , -1 , sizeof(head));
        for(int i = 0; i < m; ++i){
            int u, v;
            scanf("%d%d", &u, &v);
            add_edge(u, v);
            add_edge(v, u);
        }

        for(int i = 1; i <= n; ++i){
            if(!dfn[i]){
                dfn[i] = 1;
                tarjan_dfs(i, 0);
            }
        }
        ll rem = 0;
        for(ll i : res) {
            if(i > 0) rem += i;
        }
        rem = m - rem;
        ll ans = 1;
        for(ll i : res){
            if(i <= 0) continue;
            ans *= qpow(2, i) - 1;
            ans += mod;
            ans %= mod;
        }
        ans *= qpow(2, rem);
        ans += mod;
        ans %= mod;
        printf("%lld\n", ans);
        //for(ll i : res) cout << i << " ";
        //for(int i = 1; i < 6; ++i) cout << dfn[i] << " ";
    }
    return 0;
}

===============================================================

dp lis

题目

题意

在一个网格上有n个明星,他们出现的时间是0~ti,从(1,1)出发,每走一格消耗一个时间单位,问最多能碰见几个明星

题解

注意一个重要条件:时间是按严格升序排的,r<=500

类似于lis的dp,dp[i]表示最后碰见第i个人时的答案

ans = max(ans, dp[i]) [1 <= i <= n]

dp[i] = max(dp[i], dp[j] + 1)

在lis中更新的条件是后面的数比前面的大,在这里条件是曼哈顿距离不大于时间差(已排好序)

这样时间复杂度就是O(n^2)

对于当前明星,它存活到ti,由于网格大小的限制,所以ti - 2r之前消失的明星一定可达,而(ti-2r, ti)这段时间的明星就需要一个一个判断

接下来有一个很巧妙的将时间复杂度降到O(nr)的优化

由于[0, ti-4r]已经用来更新过(更新到ti-2r)所以这段区间将不再使用(因为它的影响已经叠加在了[ti-4r,ti-2r]这段区间上了),所以只需要看[ti-4r,ti-2r]这段区间,加上需要一个一个判断的[ti-2r,ti)这段区间。最后只需要遍历[ti-4r,ti)

时间之所以能转化成明星的个数,就是因为时间是严格有序的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//702ms/2000ms 1572kb/256mb
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
int r, n;
const int maxn = 100005;
int dp[maxn];
tuple<int, int, int> cel[maxn];
int dist(int i, int j){
    return abs(get<1>(cel[i]) - get<1>(cel[j])) + abs(get<2>(cel[i]) - get<2>(cel[j]));
}
int main() {
    memset(dp, -1, sizeof(dp));
    scanf("%d%d", &r, &n);
    get<0>(cel[0]) = 0;
    get<1>(cel[0]) = 1;
    get<2>(cel[0]) = 1;
    for(int i = 1; i <= n; ++i){
        int t, x, y;
        scanf("%d%d%d", &t, &x, &y);
        get<0>(cel[i]) = t;
        get<1>(cel[i]) = x;
        get<2>(cel[i]) = y;
    }
    dp[0] = 0;
    for(int i = 1; i <= n; ++i){
        for(int j = max(0, i - 4 * r); j < i; ++j){
            if(dp[j] == -1) continue;
            if(dist(i, j) <= get<0>(cel[i]) - get<0>(cel[j])) dp[i] = max(dp[i], dp[j] + 1);
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; ++i){
        ans = max(ans, dp[i]);
    }
    printf("%d\n", ans);
    return 0;
}

===============================================================

数论

题目

题意

给俩数p,q,求最大的x满足p%x=0,x%q!=0

题解

如果p%q!=0,x=p

否则对于pq相同的因子m,假设q中有n个m,那么就让p中m的个数减到n-1,得到一个tmp,这样就能保证p%tmp=0,tmp%q!=0,对于所有的因子产生的tmp取最大值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//46ms/1000ms 12kb/512mb
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
vector<pair<ll, ll>> vt;
ll p, q;
int main() {
    int _;
    cin >> _;
    while (_--) {
        ll ans = -1;

        cin >> p >> q;
        if(p % q != 0) ans = p;
        else{
            vt.clear();
            for(ll i = 2; i * i <= q; ++i){
                if(q % i == 0){
                    ll cot = 0;
                    while(q % i == 0) {
                        q /= i;
                        cot++;
                    }
                    vt.emplace_back(i, cot);
                }
            }
            if(q > 1) vt.emplace_back(q, 1);
            for(auto i : vt){
                ll x = i.first;
                ll y = i.second;
                ll tmp = p;
                while(tmp % x == 0){
                    tmp /= x;
                }
                for(ll j = 0; j < y - 1; ++j) tmp *= x;
                ans = max(ans, tmp);
            }
        }
        cout << ans << endl;
    }
//for(auto i : vt){
//    cout << i.first << " " << i.second << endl;
//}
    return 0;
}

===============================================================

dfs 剪枝 回溯

题目

题意

题解

重点在于剪枝,还有如何把dfs写得好看点和回溯

此处做的两个剪枝是:如果当前最小不兼容性的和大于等于ans,剪枝。如果当前要处理的集合为空,则不进行下一个集合的处理(因为下一个集合也为空(按顺序放的),这时候放哪个空集无所谓)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//308ms 32.6Mb
class Solution {
public:
    int m ;
    int ans;
    int n;
    int K;
    void dfs(vector<int>& nums, int index, vector<set<int>>& sts, int cnt){
        if(index >= n) {
            ans = min(ans, cnt);
            return ;
        }
        for(int i = 0; i < K; ++i){
            if(sts[i].empty()){
                sts[i].insert(nums[index]);
                dfs(nums, index + 1, sts, cnt);
                sts[i].erase(nums[index]);
                return ;
            }
            if(sts[i].size() < m and sts[i].find(nums[index]) == sts[i].end()){
                int tmp_cnt = cnt + nums[index] - *sts[i].rbegin();
                if(tmp_cnt >= ans) continue;
                sts[i].insert(nums[index]);
                dfs(nums, index + 1, sts, tmp_cnt);
                sts[i].erase(nums[index]);
            }
        }
    }
    
    
    int minimumIncompatibility(vector<int>& nums, int k) {
        vector<set<int>> vt(k);
        sort(nums.begin(), nums.end());
        K = k;
        ans = 0x3f3f3f3f;
        n = nums.size();
        m = n / k;
        dfs(nums, 0, vt, 0);
        if(ans == 0x3f3f3f3f) ans = -1;
        return ans;
    }
};

===============================================================

状态压缩dp 预处理

题目

题意

和上面那题一样

题解

第二种方法是状态压缩dp,(完全不会(╥╯^╰╥) )

假设n是nums的长度,m是每个集合中的元素个数

foo[i]表示集合为i时的不兼容性,只需对集合中有m个元素时处理,其他情况都是inf,注意foo[0] = 0

dp[i]表示前i个数的最小不兼容性,ans = dp[(1«n) - 1]

对于每个集合i,枚举子集,对于子集大小为m的

dp[i] = min(dp[i], foo[sub] + dp[i ^ sub])

注意初始化为dp = inf foo = inf

dp[0] = 0 foo[0] = 0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//1128ms 13.4Mb
class Solution {
public:
    int minimumIncompatibility(vector<int>& nums, int k) {
        int n = nums.size();
        int m = n / k;
        vector<int> cnt;
        vector<int> dp((1 << n) + 5, 0x3f3f3f3f);
        vector<int> foo((1 << n) + 5, 0x3f3f3f3f);
        dp[0] = 0;
        foo[0] = 0;
        for(int i = 0; i < (1 << n); ++i){
            cnt.clear();
            if(__builtin_popcount(i) == m){
                for(int j = 0; j < n; ++j){
                    if(i >> j & 1){
                        cnt.push_back(nums[j]);
                    }
                }
                sort(cnt.begin(), cnt.end());
                bool ok = true;
                for(int j = 1; j < m; ++j){
                    if(cnt[j] == cnt[j - 1]){
                        ok = false;
                        break;
                    }
                }
                if(ok) foo[i] = cnt[m - 1] - cnt[0];
            }
        }
        
        for(int i = 0; i < (1 << n); ++i){
            int sub = i;
            do{
                if(__builtin_popcount(sub) == m){
                    dp[i] = min(dp[i], foo[sub] + dp[sub ^ i]);
                }
                sub = (sub - 1) & i;
            }while(sub != i);
        }
        
        int ans = dp[(1 << n) - 1];
        if(ans == 0x3f3f3f3f) ans = -1;
        return ans;
    }
};

===============================================================

博弈

题目

题意

在平面坐标系里,有一个token,初始在原点,两人轮流操作,直到不能操作判胜负,操作是将token向上或向右移k个单位,每次移动后token离原点的距离都不能超出d

题解

记录一下看错题目,浪费一小时的惨痛经历

移动k个单位是沿一个方向

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//46ms/2000ms 0kb/256mb
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
int main() {
    ll _;
    cin >> _;
    while (_--) {
        ll d, k;
        cin >> d >> k;
        ll n = 0;
        while((n + k) * (n + k) * 2 <= d * d){
            n += k;
        }
        ll tmp = n + k;
        if(tmp * tmp + n * n <= d * d) {
            n /= k ;
            n *= 2;
            n++;
        }else{
            n /= k;
            n *= 2;
        }
        if(n & 1) puts("Ashish"); else puts("Utkarsh");

    }
    return 0;
}

===============================================================

树形dp dfs

题目

题意

给一棵树,每个节点有若干个人,从根节点开始,每个人可以选择去往一个子节点,直到这个节点没人,最后所有人都来到了叶子节点。将叶子节点的最大人数最小化

题解

画了几下图,第一感觉是贪心+搜索,如果一直这样做的话,就有可能变成一个结论题,但是细节还是很难具体化,又有点像dp,树形dp应该是逻辑最清晰的

dp[i]表示以i为根的答案,最后输出dp[1]

初始化全为0

考虑转移方程,这个节点的答案一定不小于子节点的答案

dp[i] = max{dp[ch]}

考虑把该节点的人数转移到子树上(子树的叶子节点上),贪心地选择叶子人数少的,这样不会改变最大值,答案还是max{dp[ch]}

如果叶子人数少的被填满了,也就是现在每个叶子都均匀分配了,那就对剩下的人数继续均分

这样答案就大于max{dp[ch]},答案更新为floor(sum[i] / leaf[i]),其中sum[i]表示i为根的树的总人数,leaf[i]表示i为根的树的叶子数

所以转移方程 dp[i] = max(max{dp[ch]}, floor(sum[i] / leaf[i]))

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//623ms/1000ms 30668kb/256mb
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
vector<ll> G[200005];
ll a[200005];
ll sum[200005];
ll leaf[200005];
ll dp[200005];

void dfs(ll x){
    if(G[x].size() == 0) leaf[x] = 1;
    sum[x] = a[x];
    for(ll i : G[x]){
        dfs(i);
        sum[x] += sum[i];
        leaf[x] += leaf[i];
        dp[x] = max(dp[x], dp[i]);
    }
    dp[x] = max(dp[x], ll(ceil((double)sum[x] / leaf[x])));
}

int main() {
    ll n;
    cin >> n;
    for(ll i = 2; i <= n; ++i){
        ll x;
        cin >> x;
        G[x].push_back(i);
    }
    for(ll i = 1; i <= n; ++i){
        cin >> a[i];
    }
    dfs(1);
    cout << dp[1] << '\n';
    return 0;
}

===============================================================

数论

题目

题意

有一排木板,无限多个,给r的倍数涂红色,b的倍数涂蓝色,r和b的倍数随便选一种颜色涂,将涂色的木板取出来,按原来的顺序排,问是否存在不少于k个连续的颜色相同的木板

题解

假设r > b,r和b共同的倍数一定染红色(因为红色间隔比较大),问题转化成两个红色木板间最多有几个蓝色木板,在这个区间里,第一个蓝色木板一定是所有长度为r的区间内离该区间左边红色木板最近的,由扩展欧几里得可知ax+by=c,x,y有整数解当c | gcd(a,b),所以这个最近距离是gcd(r,b),然后就可以求出最多的蓝色木板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
31ms/2000ms 0kb/256mb
#include "bits/stdc++.h"
using namespace std;
using ll = long long;
int main() {
    int _;
    cin >> _;
    while (_--) {
        int r, b, k;
        cin >> r >> b >> k;
        if(r < b) swap(r, b);
        if(floor((r - 1 - __gcd(r, b)) / (double) b) + 1 < k) puts("OBEY"); else puts("REBEL");
    }
    return 0;
}

===============================================================

思维 前缀

题目

题意

在环上有n个数字,可以对每个数字进行+1或-1的操作,问最少需要几次操作使得环中存在一个长度为n的步长为1的顺时针的递增序列

题解

最关键的思路是其中一个数肯定不变

对于每个从环中任一位置开始的数列,假设其中一个数不变,求出操作数,再对所有操作数取min,这样时间复杂度为O(n*n*n),考虑到对其他位置开始的序列,假设不变的数有可能是相同的,这样求别的数需要的操作数时就重复计算了

所以直接假设每个数为不变的数,然后遍历一遍2n个数,用前缀数组求出到这个数的操作数,对于长度为n的序列,操作数为pre[j] - pre[j - n]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<bits/stdc++.h>
using namespace std;
int a[4005];
int pre[4005];
int ans;
int main(){
    int _;
    cin >> _;
    while(_--){
        int n;
        cin >> n;
        ans = 0x3f3f3f3f;
        for(int i = 1; i <= n; ++i){
            cin >> a[i];
            a[i + n] = a[i];
        }
        for(int i = 1; i < 2 * n; ++i){
            int tmp = a[i] - i;
            for(int j = 1; j < 2 * n; ++j){
                pre[j] = pre[j - 1] + abs(a[j] - j - tmp);
                if(j >= n){
                    ans = min(ans, pre[j] - pre[j - n]);
                }
            }
        }
        cout << ans << '\n';
    }
    return 0
}