2024“钉耙编程”中国大学生算法设计超级联赛(2)

写在前面

补题地址:https://acm.hdu.edu.cn/listproblem.php?vol=65 7445 ~ 7456

以下按个人难度向排序。

dztlb 大神和妹子约会去了,于是单刷。

被魔方干爆了没找出结论于是爆搜不过妈的我是超级大傻逼这题最恶心的就是出题人放了个操作次数不大于 3 实在是太有恶意了,要是短时间内想不出结论就会想直接爆搜但是超级难写被硬控看到榜上过了这么多又不得不写呃呃呃呃啊啊啊啊啊啊啊啊啊啊啊要是次数再大点甚至任意也不至于调没调不出来赛后一想发现怎么这么脑瘫、、、、

1010

纯纯签到。

直接模拟检查结果是否唯一即可。

草鉴定为出题组里必有玩台服的马批。去年有道巅峰杯的题今年就大师杯了正好跟着台服进度的是吧我草,而且还是个玩追的追批呃呃你还点上强攻计策了是吧

哦我当年也养织姬也是追批那没事了

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::string s, t, p, q; std::cin >> s;
    int cnt[26] = {0};
    for (int i = 0; i < 8; ++ i) cnt[s[i] - 'A'] ++;

    for (int i = 0; i < 8; i += 2) t += s[i];
    for (int i = 0; i < 4; i += 2) p += t[i];
    if (p[0] == p[1]) q = p[0];
    else if (cnt[p[0] - 'A'] == cnt[p[1] - 'A']) q = "N";
    else if (cnt[p[0] - 'A'] > cnt[p[1] - 'A']) q = p[0];
    else q = p[1];

    std::cout << q << "\n";
  }
  return 0;
}

1007

签到。

注意环境变量里有 = 才输出。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::string s; std::cin >> s;
    int p = s.find("://");
    std::string t = s.substr(0, p);
    std::cout << t << "\n";
    s = s.substr(p + 3, s.length() - p);

    for (int i = 0, j = -1, len = s.length(); i < len; ++ i) {
      if (s[i] == '/') {
        int fl = (j == -1);
        for (int k = j + 1; k < i; ++ k) if (s[k] == '=') fl = 1;
        if (fl) std::cout << s.substr(j + 1, i - j - 1) << "\n";
        j = i;
      }
    }
  }
  return 0;
}

1006

树,签到。

题目描述这么长假装自己是牛逼题呃呃

由概率论可知完成节点 \(i\) 的期望天数为 \(\frac{15}{p_i}\),又每个节点是独立的,问题实际上即为找节点期望天数之和最大的以 1 为起点的链。

直接 dfs 即可,分母最大为 \(15! < 2^{60}\) 随便写就行。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
const int kM = kN << 1;
//=============================================================
int n, edgenum, head[kN], ne[kM], v[kM];
int a[kN];
struct Value {
  LL up, down;
};
Value add(Value x_, Value y_) {
  LL sup = x_.up * y_.down + x_.down * y_.up;
  LL sdown = x_.down * y_.down;
  LL d = std::__gcd(sup, sdown);
  return (Value){sup / d, sdown / d};
}
bool cmp(Value x_, Value y_) {
  LL d = std::__gcd(x_.down, y_.down);
  LL lcm = x_.down / d * y_.down;
  return (lcm / x_.down * x_.up) > (lcm / y_.down * y_.up);
}
//=============================================================
void Add(int u_, int v_) {
  v[++ edgenum] = v_;
  ne[edgenum] = head[u_];
  head[u_] = edgenum;
}
void init() {
  std::cin >> n;
  edgenum = 0;
  for (int i = 1; i <= n; ++ i) head[i] = 0;
  for (int i = 1; i < n; ++ i) {
    int u_, v_; std::cin >> u_ >> v_;
    Add(u_, v_), Add(v_, u_);
  }
  for (int i = 1; i <= n; ++ i) std::cin >> a[i];
}
Value dfs(int u_, int fa_) {
  Value ret = (Value) {15, a[u_]};
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue;
    Value retv = dfs(v_, u_);
    Value newret = add(retv, ((Value) {15, a[u_]}));
    if (cmp(newret, ret)) ret = newret;
  }
  return ret;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    init();
    Value ret = dfs(1, 0);
    std::cout << ret.up << "/" << ret.down << "\n";
  }
  return 0;
}

1011

字符串。

板题 1+1。

注意 KMP 的复杂度是 \(O(|s_1| + |s_2|)\) 的,那么不能对于每个询问串都跑一遍,否则因为每次匹配都要求 \(\operatorname{fail}\) 数组和匹配,复杂度里就会带一个 \(O(m\times |A| + m\times |C|)\) 就过不去了。

发现 \(C\)\(\operatorname{fail}\) 可以预处理,那么可以在 \(B'\) 里匹配 \(C\) 时使用 KMP,所需时间复杂度 \(O(|C| + \sum |B'|)\)

\(A\) 里匹配 \(B\) 有两种做法,一种是对所有 \(B\) 建 AC 自动机然后用 \(A\) 匹配检查可以匹配到哪些终止状态,一种是对 \(A\) 建后缀自动机对于每一个 \(B\) 都匹配检查是否可以作为子串完全匹配。复杂度均是 \(O(|A| + \sum |B|)\) 的。

注意自动机的多测清空。场上抄了个 SAM 多测不清空吃四发我草我是纯傻逼。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
int n, failc[kN];
std::string a, b, bb, c;
//=============================================================
namespace SAM {
  const int kNode = kN << 2;
  int nodenum = 1, last = 1, tr[kNode][26], len[kNode], link[kNode];
  void Insert(int ch_) {
    int p = last, now = last = ++ nodenum;
    len[now] = len[p] + 1;
    for (; p && !tr[p][ch_]; p = link[p]) tr[p][ch_] = now;

    if (!p) {
      link[now] = 1;
      return ;
    }

    int q = tr[p][ch_];
    if (len[q] == len[p] + 1) {
      link[now] = q;
      return ;
    }

    int newq = ++ nodenum;
    memcpy(tr[newq], tr[q], sizeof (tr[q]));
    len[newq] = len[p] + 1;
    link[newq] = link[q], link[q] = link[now] = newq;
    for (; p && tr[p][ch_] == q; p = link[p]) tr[p][ch_] = newq;
  }
  void init() {
    for (int i = 1; i <= nodenum; ++ i) {
      for (int j = 0; j < 26; ++ j) {
        tr[i][j] = 0;
      }
      len[i] = link[i] = 0;
    }
    nodenum = 1, last = 1;
  }
  bool query() {
    int now = 1;
    for (int i = 0, lenb = b.length(); i < lenb; ++ i) {
      if (tr[now][b[i] - 'a']) now = tr[now][b[i] - 'a'];
      else return false;
    }
    return true;
  }
}
void init() {
  int lenc = c.length(), lena = a.length();
  failc[0] = -1;
  for (int i = 1, j = -1; i < lenc; ++ i) {
    while (j != -1 && c[i] != c[j + 1]) j = failc[j];
    if (c[i] == c[j + 1]) ++ j;
    failc[i] = j;
  }

  SAM::init();
  for (int i = 0; i < lena; ++ i) SAM::Insert(a[i] - 'a');
}
bool checkc() {
  int lenbb = bb.length(), lenc = c.length();
  for (int i = 0, j = -1; i < lenbb; ++ i) {
    while (j != -1 && bb[i] != c[j + 1]) j = failc[j];
    if (bb[i] == c[j + 1]) ++ j;
    if (j == lenc - 1) return true;
  }
  return false;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    std::cin >> a >> c;
    init();
    
    std::vector<int> ans;
    for (int i = 1; i <= n; ++ i) {
      std::cin >> b >> bb;
      if (SAM::query() && checkc()) ans.push_back(i);  
    }
    for (int i = 0, sz = ans.size(); i < sz; ++ i) {
      std::cout << ans[i];
      if (i != sz - 1) std::cout << " ";
    }
    std::cout << "\n";
  }
  return 0;
}

1001

构造。

呃呃脑瘫构造。

边不可重复贡献,则构成鸡爪的上界为 \(\left\lfloor\frac{n}{3}\right\rfloor\),且一定可以达到这个上界;点作为中心不可重复贡献,考虑每次新加入一个中心时对图的影响。

先考虑所有边均有贡献的答案。先手玩下用 9 条边构成 3 个鸡爪的答案。发现一种最优的构造是:

(1, 2), (1, 3), (1, 4)
(1, 5), (2, 3), (2, 4)
(1, 6), (2, 5), (3, 4)

然后考虑大于 3 个鸡爪时,发现每加入一个中心 \(i\) 前均有边 \((1, i), (2, i), (3, i)\),于是仅需连边 \((3, i + 1), (2, i + 2), (1, i + 3)\),表示从中心分别为 123 的三个鸡爪中抢三条边,然后分别为他们补一条边。显然这样的构造可以保证所有边均以 123 为端点,且保证了 123 均参与了所有鸡爪的构成,可以保证为字典序最小的最优解。

然后需要特判有多余的边的情况。当 \(r = n\bmod 3 \not= 0\) 时,多余的 \(r\) 条边是不参与鸡爪构成的,为了保证字典序最小,则此时方案为构造 \(n - r\) 的答案,再并加上 \(r\) 条以 1 为端点的边。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 1e5 + 10;
//=============================================================
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
    
  int T; std::cin >> T;
  while (T --) {
    int n, cnt = 0; std::cin >> n;
    std::vector<pr<int,int> > ans;
    for (int i = 1, maxnode = 1; i <= n && cnt < n; ++ i) {
      if (cnt + 3 > n) {
        while (cnt < n) ans.push_back(mp(1, ++ maxnode)), ++ cnt;
      } else if (i == 1) {
        if (cnt < n) ans.push_back(mp(1, 2)), ++ cnt;
        if (cnt < n) ans.push_back(mp(1, 3)), ++ cnt;
        if (cnt < n) ans.push_back(mp(1, 4)), ++ cnt;
      } else if (i == 2) {
        if (cnt < n) ans.push_back(mp(1, 5)), ++ cnt;
        if (cnt < n) ans.push_back(mp(2, 3)), ++ cnt;
        if (cnt < n) ans.push_back(mp(2, 4)), ++ cnt;
      } else if (i == 3) {
        if (cnt < n) ans.push_back(mp(1, 6)), ++ cnt;
        if (cnt < n) ans.push_back(mp(2, 5)), ++ cnt;
        if (cnt < n) ans.push_back(mp(3, 4)), ++ cnt;
      } else {
        if (cnt < n) ans.push_back(mp(1, i + 3)), ++ cnt;
        if (cnt < n) ans.push_back(mp(2, i + 2)), ++ cnt;
        if (cnt < n) ans.push_back(mp(3, i + 1)), ++ cnt;
      }
      maxnode = i + 3;
    }
    std::sort(ans.begin(), ans.end());
    for (auto x: ans) std::cout << x.first << " " << x.second << "\n";
  }
  return 0;
}

1003

思维,手玩。

出这种和生活实际相关的题对有经验的人相当有优势。我他妈完全不会玩魔方一面我都复原不了光企图在脑子里想魔方怎么转的就搞红温了妈的

发现每次转动侧面时,角块上各个面的相对顺序是不会改变的,相当于仅仅是移动到了另外一个位置。而如果将角块两面互换则会影响这个性质。考虑对正常的魔方八个角的形态按照一定枚举面的顺序记录一下,我采用的顺序是选择面 1 或者面 6 作为上面,角块的尖尖正对自己时,逆时针转时各面的顺序。顺序如下图所示:

20240722205754.jpg

检查给定的魔方八个角的形态是否被记录即可,若无记录则这个角就是被搞坏的。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int corner[8][3][2] = {
  //第一个位置所在面(面 1 或者面 6)作为上面,角块的尖尖正对自己时,逆时针转时的顺序
  1, 4, 4, 12, 4, 1, //152
  1, 6, 4, 9, 4, 10, //145
  3, 4, 4, 3, 4, 4,  //123
  3, 6, 4, 6, 4, 7,  //134

  7, 4, 6, 4, 6, 3,  //632
  7, 6, 6, 7, 6, 6,  //643
  9, 6, 6, 10, 6, 9, //654
  9, 4, 6, 1, 6, 12  //625 
};
const char yes[10][15] = {
  "",
  " ***111******",
  " ***111******",
  " ***111******",
  " 222333444555",
  " 222333444555",
  " 222333444555",
  " ***666******",
  " ***666******",
  " ***666******"
};
//=============================================================
char cube[10][15];
std::set<std::string> yescorner; 
//=============================================================
void init() {
  for (int i = 0; i < 8; ++ i) {
    std::string s;
    for (int j = 0; j < 3; ++ j) s.push_back(yes[corner[i][j][0]][corner[i][j][1]]);
    while (s[0] != '1' && s[0] != '6') s = s.substr(1, 2) + s[0];
    yescorner.insert(s);
  }
}
bool check() {
  for (int i = 0; i < 8; ++ i) {
    std::string s;
    for (int j = 0; j < 3; ++ j) s.push_back(cube[corner[i][j][0]][corner[i][j][1]]);
    while (s[0] != '1' && s[0] != '6') s = s.substr(1, 2) + s[0];
    if (yescorner.count(s)) continue;

    std::sort(s.begin(), s.end());
    std::cout << s[0] << " " << s[1] << " " << s[2] << "\n";
    return true;
  }
  return false;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  // std::ios::sync_with_stdio(0, std::cin.tie(0);
  
  init();
  // for (auto x: yescorner) std::cout << x << "\n";
  int T; scanf("%d", &T);
  while (T --) {
    for (int i = 1; i <= 9; ++ i) scanf("%s", cube[i] + 1);
    if (!check()) printf("No problem\n");
  }
  return 0;
}

1008

手玩,思维

我草这个拆的套路好像什么时候见过啊感觉好厉害

发现从原图中每个节点生长后得到的子图之间的连边是不变的,且得到的子图一定是一条链,则若直径经过原图中该节点对应的子图,则该节点生长后所有节点都可以取到贡献。于是考虑将每个节点权值设为生长后的点数,再在原树上求带权直径即可。

然而生长后的点数在 \(m\) 较大时显然会贼几把大存不下而且取模之后对大小比较会产生影响。手玩下发现生长后的子图中仅有 1/2/3 度点,1 度点为叶子将不再生长,于是仅需考虑 2/3 度点后续的贡献。更进一步地,对于一个度数为 \(d(d\ge 1)\) 的节点,生长一次后会变为 2 个 2 度点和 \(d-2\) 个 3 度点,则可得到 生长 \(m\) 次后的总点数为:

\[(d - 1)\times (2^m - 1) + 1 = (d - 1)\times 2^m- (d - 2) \]

\(m\) 较小时上式不算太大可以直接算。\(m\) 较大时乘积中两项可以分开考虑,第一项 \((d-1)\times 2^m\) 的影响压倒性地大,于是考虑把节点权值表示成 \((d - 1, -(d-2))\) 的二元组,优先级为 \(d - 1\) 降序第一优先级,\(-(d - 2)\) 降序第二优先级,最大化直径上点的二元组之和最后还原答案即可。

\(n\le 10^5\),考虑到为了使影响压倒性地大上式中 \(2^m\) 应当大出度数 \(d\) 一个数量级,判定 \(m\) 较小的阈值定在 20 左右即可。

总时间复杂度 \(O(n + \log m)\) 级别,\(\log m\) 是因为要做快速幂。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 1e5 + 10;
const int kM = kN << 1;
const LL p = 1e9 + 7;
//=============================================================
int n, m, edgenum, head[kN], ne[kM], v[kM];
LL ans, pw2[21], val1[kN], f1[kN][2];

struct Value {
  LL x, y;
  Value() {}
  Value(LL x_, LL y_) {
    this->x = x_, this->y = y_;
  }
  bool operator >(const Value sec_) const {
    if (x != sec_.x) return x > sec_.x;
    return y > sec_.y;
  }
  bool operator ==(const Value sec_) const {
    return x == sec_.x && y == sec_.y;
  }
  bool operator >=(const Value sec_) const {
    return (*this) > sec_ || (*this) == sec_;
  }
  bool operator <(const Value sec_) const {
    return !((*this) >= sec_); 
  }
  Value operator +(const Value sec_) const {
    return Value(this->x + sec_.x, this->y + sec_.y);
  }
} ans2, val2[kN], f2[kN][2];
//=============================================================
void addedge(int u_, int v_) {
  v[++ edgenum] = v_;
  ne[edgenum] = head[u_];
  head[u_] = edgenum;
}
LL qpow(LL x_, LL y_) {
  LL ret = 1;
  while (y_) {
    if (y_ & 1) ret = ret * x_ % p;
    x_ = x_ * x_ % p, y_ >>= 1ll; 
  }
  return ret;
}
void dfs1(int u_, int fa_) {
  f1[u_][0] = f1[u_][1] = 0;
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue;
    dfs1(v_, u_);
    if (f1[v_][0] >= f1[u_][0]) f1[u_][1] = f1[u_][0], f1[u_][0] = f1[v_][0];
    else if (f1[v_][0] > f1[u_][1]) f1[u_][1] = f1[v_][0];
  }
  ans = std::max(ans, f1[u_][0] + f1[u_][1] + val1[u_]);
  f1[u_][0] += val1[u_], f1[u_][1] += val1[u_];
}
void solve1() {
  for (int i = 1; i <= n; ++ i) {
    int d = 0;
    for (int j = head[i]; j; j = ne[j]) ++ d;
    val1[i] = 1ll * (d - 1) * pw2[m] - d + 2;
  }
  dfs1(1, 0);
}
void dfs2(int u_, int fa_) {
  f2[u_][0] = f2[u_][1] = Value(0, 0);
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue;
    dfs2(v_, u_);
    if (f2[v_][0] >= f2[u_][0]) f2[u_][1] = f2[u_][0], f2[u_][0] = f2[v_][0];
    else if (f2[v_][0] > f2[u_][1]) f2[u_][1] = f2[v_][0];
  }
  ans2 = std::max(ans2, f2[u_][0] + f2[u_][1] + val2[u_]);
  f2[u_][0] = f2[u_][0] + val2[u_]; 
  f2[u_][1] = f2[u_][1] + val2[u_];
}
void solve2() {
  for (int i = 1; i <= n; ++ i) {
    int d = 0; 
    for (int j = head[i]; j; j = ne[j]) ++ d;
    val2[i] = Value(d - 1, -(d - 2));
  }
  dfs2(1, 0);
  ans = (ans2.x * qpow(2, m) % p + ans2.y % p + p) % p;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  pw2[0] = 1;
  for (int i = 1; i <= 20; ++ i) pw2[i] = 2ll * pw2[i - 1];

  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m;
    ans = edgenum = 0;
    ans2 = Value(0, 0);
    for (int i = 1; i <= n; ++ i) head[i] = 0;
    
    for (int i = 1; i < n; ++ i) {
      int u_, v_; std::cin >> u_ >> v_;
      addedge(u_, v_), addedge(v_, u_);
    }
    (m <= 20) ? solve1() : solve2();
    std::cout << ans << "\n";
  }
  return 0;
}
/*
1
5 10000000
1 2
1 3
2 4
3 5

1
7 100000
1 2
1 3
2 4
2 5
2 6
3 7
*/

1012

并查集,哈希,启发式合并(按秩合并)

我草原题大战:https://www.luogu.com.cn/problem/P8026

发现 \(d\times n\) 不大,每个图的连通性都可以单独用并查集维护。记图 \(i(1\le i\le d + 1)\) 的并查集上节点 \(u\) 的祖先为 \(\operatorname{fa}_{i, u}\),考虑题目给定限制:点对 \((u, v)\) 在所有图上均联通——在并查集上体现为在所有图上点 \(u, v\) 的祖先均相同,即有下列两数列相等:

\[\operatorname{fa}_{1, u}\operatorname{fa}_{2, u}\cdots \operatorname{fa}_{d+1, u} = \operatorname{fa}_{1, v}\operatorname{fa}_{2, v}\cdots \operatorname{fa}_{d+1, v} \]

\(d\le 100\),考虑将上述数列看做一个长为 \(d+1\) 的字符串,则询问变为统计 \(n\) 个节点一一对应的 \(n\) 种字符串之间有哪些两两相等。考虑哈希判重,则显然可以开个桶记录每种哈希值 \(h\) 的出现次数 \(\operatorname{cnt}_{h}\),对答案贡献即为 \(\frac{h\times (h - 1)}{2}\)

然后考虑在并查集维护连通性的过程中维护上述字符串的哈希值。发现每次加边合并过程中,受影响的字符有仅有祖先被更改的位置对应的字符。发现通过并查集按秩合并可以使得每次祖先被更改的位置均摊为 \(\log n\) 个,于是直接在按秩合并时大力修改祖先和哈希值并更新答案即可。

实现时偷懒用 map 当桶,总时间复杂度 \(O(m + k + dn\log^2 n)\) 级别。

这题没有特意卡哈希实现,于是可以直接给 \(1\sim d + 1\) 这些位置上分配随机权值 \(\operatorname{value}_i\),则对于节点 \(u\) 的字符串哈希值可表示为 \(\sum_{1\le i\le d+1}\operatorname{value}_i\times \operatorname{fa}_{i, u}\),然后直接自然溢出啥事儿没有。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
const int kN = 5e4 + 10;
const int kD = 110;
//=============================================================
int n, m, d, k, fa[kD][kN];
std::vector<int> tree[kD][kN];
LL ans;
ULL value[kD], hash[kN];
std::unordered_map <ULL, int> cnt;
//=============================================================
int find(int d_, int x_) {
  return (fa[d_][x_] == x_) ? x_ : find(d_, fa[d_][x_]);
}
void merge(int w_, int u_, int v_) {
  int fu = find(w_, u_), fv = find(w_, v_);
  if (fu == fv) return ;
  
  if (tree[w_][fu].size() > tree[w_][fv].size()) std::swap(fu, fv);
  fa[w_][fu] = fv;
  for (auto x: tree[w_][fu]) {
    tree[w_][fv].push_back(x);
    fa[w_][x] = fv;

    ans -= 2ll * cnt[hash[x]] - 1, cnt[hash[x]] = cnt[hash[x]] - 1;
    hash[x] += (fv - fu) * value[w_];
    ans += 2ll * cnt[hash[x]] + 1, cnt[hash[x]] = cnt[hash[x]] + 1;
  }
  tree[w_][fu].clear();
}
void init() {
  std::cin >> n >> m >> d >> k;
  cnt.clear();
  for (int i = 1; i <= d + 1; ++ i) value[i] = 1ll * rand() * rand();
  for (int i = 1; i <= n; ++ i) {
    fa[1][i] = i;
    tree[1][i].clear(), tree[1][i].push_back(i);
  }
  for (int i = 1; i <= m; ++ i) {
    int u_, v_; std::cin >> u_ >> v_;
    merge(1, u_, v_);
  }

  for (int i = 2; i <= d + 1; ++ i) {
    for (int j = 1; j <= n; ++ j) {
      fa[i][j] = fa[1][j];
      tree[i][j].clear();
      for (auto x: tree[1][j]) tree[i][j].push_back(x);
    }
  }

  ans = 0;
  cnt.clear();
  for (int u = 1; u <= n; ++ u) {
    hash[u] = 0;
    for (int i = 1; i <= d + 1; ++ i) {
      hash[u] += 1ll * value[i] * fa[1][u];
    }
    cnt[hash[u]] = cnt[hash[u]] + 1;
  }
  for (auto x: cnt) 
    ans += 1ll * x.second * (x.second - 1);
}
void addedge(int u_, int v_, int w_) {
  merge(w_, u_, v_);
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  srand(time(0));
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    init();
    // std::cout << ans << "\n";
    for (int i = 1; i <= k; ++ i) {
      int u_, v_, w_; std::cin >> u_ >> v_ >> w_;
      addedge(u_, v_, w_);
      std::cout << ans / 2ll << "\n";
    }
  }
  return 0;
}

1002

状压 DP。

妈的这东西好难调,弃了!

发现贷款这个东西可以不用管,仅需考虑如何获得最大价值即可。

发现 \(K\) 超级小,\(n\times m\times K\) 也不大,甚至 \(n^2\times m^2\times K\) 都是可以承受的,于是显然考虑状压 DP,设状态 \(f_{x, y, s}\) 表示到达位置 \((x, y)\),击杀的怪物集合为 \(s\) 时可以获得的最大价值之和,转移直接枚举移动位置即可。

直接转移不太好控制转移的顺序,可能有出现循环转移,于是用 SPFA 进行转移求最长路即可。

写在最后

参考:https://www.cnblogs.com/cjjsb/p/18316724

学到了什么:

  • 1011:KMP 的唯一优势,只有码量!
  • 1003:不变的!
  • 1008:两项之和贡献,且一方贡献有压倒性优势,考虑拆成二元组规定优先级求和。
  • 1012:重复元素维护与组合计数,转化为哈希值的计数。

我是傻逼。

唉,闪耀!优俊少女!死了!

哎呦我草 BA 的新音声真有感觉什么时候出玛丽的音声啊我草我要冲爆

哎呦我草这个夏和小的音声啊听着一秒不昏过去的都是神人了:【【中文字幕/碧蓝档案】月雪宫子(泳装)ASMR 「在你我二人的小岛上共道早安」】 https://www.bilibili.com/video/BV1zS411w7X6

posted @ 2024-07-22 21:46  Luckyblock  阅读(338)  评论(1编辑  收藏  举报