The 2023 ICPC Asia Shenyang Regional Contest (The 2nd Universal Cup. Stage 13: Shenyang)

写在前面

比赛地址:https://codeforces.com/gym/104869

以下按个人难度向排序。

唉三人真爽吧六题手快打进金尾,手上还剩 1h 两题能开真是非常梦幻的一场。

C

签到。

出题人这么良心还给了张图,实在是太好了。

//
/*
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 x, y, ans; std::cin >> x >> y;
  if (x == 0) {
    if (y == 0) ans = 4;
    if (y == 1) ans = 4;
    if (y == 2) ans = 6;
  }
  if (x == 1) {
    if (y == 0) ans = 3;
    if (y == 1) ans = 3;
    if (y == 2) ans = 4;
  }
  if (x == 2) {
    if (y == 0) ans = 2;
    if (y == 1) ans = 2;
    if (y == 2) ans = 2;
  }
  std::cout << ans << "\n";
  return 0;
}

J

博弈。

dztlb 大神秒了,牛逼。

发现每次操作 \((u, v)\) 时,\(u, v\) 一定都不为叶节点,否则新的树一定同构。若均不为叶子则一定不同构。

然后发现每次操作相当于将 \(u\) 变成新的叶子,并将其子树均接到 \(v\) 上,发现此时会使 \(u\) 的度数变为 1,\(v\) 的度数增加,其他点度数不变。

则每次操作都会使叶子数恰好 +1,\(n-1\) 个叶子时游戏结束,于是仅需判断初始叶子数和 \(n-1\) 的奇偶性。

Code by dztlb:

#include<bits/stdc++.h>
using namespace std;
const int N=55;
#define int long long
int n;
int du[N];
signed main(){
	cin>>n;
	for(int i=1,u,v;i<n;++i){
		cin>>u>>v;
		++du[u],++du[v];
	}
	int cnt=n-1;
	for(int i=1;i<=n;++i){
		if(du[i]==1) --cnt;
	}
	if(cnt%2==1) puts("Alice");
	else puts("Bob");
	return 0;
}

E

DP,最短路

我去这不是经典的野人传教士过河问题吗。

数据范围很小显然考虑 DP,记 \(f_{i, j, 0/1}\) 表示当前位于此岸/彼岸,使得此岸有 \(i\) 只羊 \(j\) 只狼(保证合法)时,需要运输的最小次数。转移时枚举当前这一次运输有几只羊几只狼即可。

发现运输过程中此岸的羊的数量一定是单调不增的,有拓扑序存在,但是可能会从彼岸把狼运回来造成环形的转移。不过由于求的是最小次数,于是套路地使用最短路进行转移,将各个状态转换为节点,首先 \(O(n^4)\) 预处理各个状态间转移的作为边,因为边权值均为 1,直接跑 01 BFS 转移即可。

复杂度 \(O(n^4)\) 级别。

赛时比较急忘了边权值只有 1 了于是写了 dijkstra 反正能过。

Code by wenqizhi:

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

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 5e4 + 5;
const int inf = 0x3f3f3f3f;
int x, y, p, q;
int dis[N], vis[N];
int id(int i, int j, int k){ return i * 102 + j + 1 + k * 103 * 103; }
vector<int> V[N];
priority_queue< pair<int, int> > Q;

void dijkstra()
{
    Q.push(pair<int, int>(0, id(x, y, 0)));
    while(!Q.empty())
    {
        int now = Q.top().second ;
        Q.pop();
        if(vis[now]) continue;
        vis[now] = 1;
        for(auto v : V[now])
        {
            if(dis[v] > dis[now] + 1)
            {
                dis[v] = dis[now] + 1;
                Q.push(pair<int, int>(-dis[v], v));
            }
        }
    }
}

int main()
{
    x = read(), y = read(), p = read(), q = read();
    for(int i = 0; i <= x; ++i)
        for(int j = 0; j <= y; ++j)
            dis[id(i, j, 1)] = dis[id(i, j, 0)] = inf;
    dis[id(x, y, 0)] = 0;
    for(int i = 0; i <= x; ++i)
        for(int j = 0; j <= y; ++j)
            for(int k = 0; k <= 1; ++k)
                for(int t = 0; t <= p; ++t)
                    for(int a = 0, b = t; a <= t; ++a, --b)
                    {
                        if(k == 0)
                        {
                            if(a > i) continue;
                            if(b > j) continue; // 不能无中生有
                            if(x - i > 0 && y - j > x - i + q) continue; // 对岸不合法
                            if(i - a > 0 && j - b > i - a + q) continue; // 运走后此岸不合法
                            V[id(i, j, k)].emplace_back(id(i - a, j - b, k ^ 1));
                        }else
                        {
                            if(a > x - i) continue;
                            if(b > y - j) continue;
                            if(i > 0 && j > i + q) continue;
                            if(x - i - a > 0 && y - j - b > x - i - a + q) continue;
                            V[id(i, j, k)].emplace_back(id(i + a, j + b, k ^ 1));
                        }
                    }
    dijkstra();
    int ans = inf;
    for(int i = 0; i <= y; ++i) ans = min(ans, dis[id(0, i, 1)]);
    if(ans == inf){ printf("-1\n"); return 0; }
    printf("%d\n", ans);
    return 0;
}

K

贪心,二分,权值线段树

考虑对于某个 \(k\) 是否合法,发现最优的构造方案一定是将前 \(k\) 大的正数放到数列开头,中间放所有负数,最后放剩余的所有正数。若最后一段的正数之和不大于负数之和的绝对值(即加上这些正数不会使得最大值上升),则 \(k\) 合法。

记当前数列中有 \(c\) 个正数,发现若答案为 \(\operatorname{ans}\),则合法的 \(k\) 的取值一定构成一段长度为 \(\operatorname{ans}\) 连续的区间 \([c-\operatorname{ans}+1, c]\),其中 \(c-\operatorname{ans}+1\) 为最小的合法的 \(k\),即对于 \(k\le c-\operatorname{ans}\) 即使按照上述方案构造,最后一段的正数之和仍大于负数之和的绝对值。

负数之和可以很方便的维护,发现仅需找到最大的 \(\operatorname{ans}\) 使数列前 \(\operatorname{ans}\) 小正数之和不大于负数之和即可。则可以对所有正数维护动态开点的权值线段树,在权值线段树上二分即得。

总时间复杂度 \(O((n + q)\log v)\)

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const int kInf = 1e9;
//=============================================================
int n, q, a[kN];
LL minus;
//=============================================================
namespace seg {
  #define ls ((lson[now_]))
  #define rs ((rson[now_]))
  #define mid ((L_+R_)>>1)
  const int kNode = kN << 6;
  int rt, nodenum, lson[kNode], rson[kNode], cnt[kNode];
  LL sum[kNode];
  void insert(int L_, int R_, int pos_, int val_, int &now_ = rt) {
    if (!now_) now_ = ++ nodenum;
    
    sum[now_] += 1ll * pos_ * val_;
    cnt[now_] += val_;
    if (L_ == R_) return ;

    if (pos_ <= mid) insert(L_, mid, pos_, val_, ls);
    else insert(mid + 1, R_, pos_, val_, rs);
  }
  int query(int L_, int R_, LL sum_, int cnt_, int now_ = rt) {
    if (L_ == R_) {
      return cnt_ + std::min(1ll * cnt[now_], 1ll * sum_ / L_);
    }
    if (sum_ >= sum[ls]) return query(mid + 1, R_, sum_ - sum[ls], cnt_ + cnt[ls], rs);
    return query(L_, mid, sum_, cnt_, ls);
  }
  #undef ls
  #undef rs
  #undef mid
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n >> q;
  for (int i = 1; i <= n; ++ i) {
    std::cin >> a[i];
    if (a[i] <= 0) minus += -a[i];
    else seg::insert(1, kInf, a[i], 1);
  }

  seg::query(1, kInf, minus, 0);

  while (q --) {
    int x, v; std::cin >> x >> v;
    if (a[x] <= 0) minus -= -a[x];
    else seg::insert(1, kInf, a[x], -1);

    a[x] = v;
    if (a[x] <= 0) minus += -a[x]; 
    else seg::insert(1, kInf, a[x], 1);

    std::cout << seg::query(1, kInf, minus, 0) + 1 << "\n";
  }
  return 0;
}
/*
6 1
1000000000 -1 -1 -1 -1 -1
2 -1

6 1
1 1 1 1 100000 -3
1 1
*/

M

数学,结论,手玩

首先显然从 \(s\) 出发到 \(t\) 等价于从 \(s\oplus s = 0\) 出发到 \(t'=s\oplus t\),因为仅考虑不同的位,且边权值为不同的位的编号,则对所有点做异或并无任何影响。于是仅需考虑从 0 开始走到达 \(t'\) 的路径长什么样。

大力手玩之后发现(赛时手玩到了 \(t' = (10000000)_2\) 妈的实在是太变态了),可以将节点编号的二进制串每相邻两位分为一个循环节,循环节的变化一定为:\((00)_2\rightarrow (01)_2 \rightarrow (11)_2 \rightarrow (10)_2 \rightarrow (00)_2\)。当内层循环节循环一次,就会使得次内层循环节变化,然后内层再重复上述的变化。显然循环节的长度一定是 4 的幂,于是显然想到通过判断每个循环节的形态,可直接计算出第一次到达 \(t\) 的步数,以及在 \(t\) 上循环 \(k\) 次的步数,求和即为答案。

发现第 1 次到达 \(t\) 时,经过的步数取决于 \(t'\) 中各个循环节的形态,若第 \(i\) 个循环节为 \(c\),则由上述图的形态可知贡献为 \(c\times 4^i\),直接累加即可;进一步地,发现 \(t\) 能经过 \(k\) 次,当前仅当 \(t\) 最后有至少 \(k\) 个循环节为 \((00)_2\) 的形态,否则无解。且易发现重复经过的步数依次为:\(4^1, 4^2, \cdots, 4^k-1\),再直接累加即可。

注意特判 \(s=t\) 的情况,此时步数直接等比数列求和即可得到。

Code by wenqizhi:

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

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const ll mod = 1e9 + 7;
const int N = 1e6 + 5;
char s[N], t[N], SS[N], TT[N];
ll K, b[N], a[N];
ll f[N];

void init()
{
    f[0] = 1;
    for(int i = 1; i <= 1000000; ++i) f[i] = f[i - 1] * 4ll % mod;
    for(int i = 2; i <= 1000000; ++i) f[i] = (f[i] + f[i - 1]) % mod;
    f[0] = 0;
}

ll qpow(ll a, ll b, ll mod)
{
    ll ans = 1;
    while(b)
    {
        if(b & 1) ans = ans * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return ans;
}

void solve()
{
    scanf("%s%s", SS + 1, TT + 1);
    int len1 = strlen(SS + 1), len2 = strlen(TT + 1);
    int len = max(len1, len2);
    if(len % 2 == 1) ++len;
    K = read() - 1;
    for(int i = len; i >= 1; --i)
    {
        if(len - i + 1 <= len1) s[i] = SS[len1 - (len - i + 1) + 1];
        else s[i] = '0';
        if(len - i + 1 <= len2) t[i] = TT[len2 - (len - i + 1) + 1];
        else t[i] = '0';
    }
    for(int i = 1; i <= len; ++i)
        if(s[i] == '1') t[i] ^= 1;
    for(int i = 2; i <= len; i += 2)
    {
        if(t[i - 1] == '0' && t[i] == '0') a[i / 2] = 0;
        if(t[i - 1] == '0' && t[i] == '1') a[i / 2] = 1;
        if(t[i - 1] == '1' && t[i] == '1') a[i / 2] = 2;
        if(t[i - 1] == '1' && t[i] == '0') a[i / 2] = 3;
    }
    len >>= 1;
    int newlen = 0, flag = 0;
    for(int i = 1; i <= len; ++i)
    {
        if(!flag && a[i] == 0) continue;
        else flag = 1, b[++newlen] = a[i];
    }
    len = newlen;
    if(len == 0) b[++len] = 0;
    if(len == 1 && b[1] == 0)
    {
        printf("%lld\n", 4ll * (qpow(4, K, mod) - 1 + mod) % mod * qpow(3, mod - 2, mod) % mod);
        return ;
    }
    ll ans = 0, last = 1;
    for(int i = 1; i <= len; ++i)
    {
        if(b[i] == 0) continue;
        int k = len - i;
        last = i;
        ans = (ans + b[i] + f[k] * b[i]) % mod;
    }
    if(K > len - last){ printf("-1\n"); return ; }
    ans = (ans + f[K]) % mod;
    printf("%lld\n", ans);
}

int main()
{
    int T = read();
    init();
    while(T--) solve();
    return 0;
}

D

字符串,计数。

是了这题是我的,虽然到最后也没写出来呃呃要写出来了就复刻美滋滋的神之一役了。

我的写法有点麻烦,全是经典问题缝合,不太推荐对字符串没啥感觉的选手参考。

发现仅需处理 \(s[p:q]\) 长度大于等于 \(t[u:v]\) 的情况即可;小于的情况可以翻转两个字符串,再对称地套用上述算法即可。

我的做法是考虑在 \(s\) 中作为平方串前一半的区间 \([i, j]\),然后考虑实际选择的区间 \([i, p](j\le p\le j + (j - i))\) 的右端点 \(p\) 可以延伸到什么位置,再考虑 \(t\) 中对应的可以选择的互补的串的数量。

发现若右端点 \(p\) 可以继续向右延伸,说明后缀 \(s[i:n]\) 与后缀的后缀 \(s[j+1:n]\) 有公共前缀,于是考虑对所有后缀 \(s[i:n]\) 都跑一遍 Z 函数,则区间 \([i,j]\) 右端点最长可以延伸的位置即 \(\min(z_{j + 1}, j + (j - i))\)

然后考虑 \(t\) 中对应的可以选择的互补的串的数量,发现即求子串 \(s[i:j], s[i+1: j], s[i+2:j], \cdots\)\(t\) 中的出现次数。考虑维护 \(\operatorname{cnt}_{j, l}\) 表示 \(s\) 的子串 \(s[j:l]\)\(t\) 中的出现次数。这显然也是经典问题,考虑将两个串都取反得到 \(s', t'\),然后枚举 \(s'\) 的后缀 \(s[j:n]\)(即原串 \(s\) 的所有前缀)跑 KMP,然后在 \(t'\) 上匹配即得每个位置最多可以匹配几位,再套路地在 \(\operatorname{fail}\) 树上转移一下即可求得 \(\operatorname{cnt}_{j,\cdots}\)。在对 \(\operatorname{cnt}_j\) 做个前缀和即可直接查询上述信息。

总时间复杂度 \(O(n^2)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5010;
//=============================================================
int z[kN];
int cnt[kN][kN];
int fail[kN];
LL ans;
//=============================================================
void initreverse(std::string &s, std::string &t, int len1_, int len2_, int pos_) {
  int p = len1_ - pos_ + 1;
  for (int i = 0; i <= len1_ + 1; ++ i) cnt[p][i] = 0;

  fail[1] = 0;
  for (int i = 2, j = 0; i <= len1_; ++ i) {
    while (j && s[pos_ + i - 1] != s[pos_ + j]) j = fail[j];
    if (s[pos_ + i - 1] == s[pos_ + j]) ++ j;
    fail[i] = j;
  }
  for (int i = 1, j = 0; i <= len2_; ++ i) {
    while (j && t[i] != s[pos_ + j]) j = fail[j];
    if (t[i] == s[pos_ + j]) ++ j;
    if (j) ++ cnt[p][j];
  }
  for (int i = len1_; i; -- i) cnt[p][fail[i]] += cnt[p][i];
  for (int i = 1; i <= len1_; ++ i) cnt[p][i] += cnt[p][i - 1];
}
void init(std::string &s, int len_, int pos_) {
  for (int i = 1; i <= len_ + 1; ++ i) z[i] = 0;
  z[pos_] = len_ - pos_ + 1;
  for (int i = 2, l = 0, r = 0; i <= len_ - pos_ + 1; ++ i) {
    if (i <= r && z[pos_ - 1 + i - l + 1] < r - i + 1) {
      z[pos_ - 1 + i] = z[pos_ - 1 + i - l + 1];
    } else {
      z[pos_ - 1 + i] = std::max(0, r - i + 1);
      while (i + z[pos_ - 1 + i] <= len_ - pos_ + 1 && 
            s[pos_ - 1 + z[pos_ - 1 + i] + 1] == s[pos_ - 1 + i + z[pos_ - 1 + i]]) {
        ++ z[pos_ - 1 + i];
      }
    }
    if (i + z[pos_ - 1 + i] - 1 > r) l = i, r = i + z[pos_ - 1 + i] - 1;
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::string s, t;
  std::cin >> s >> t;
  int n = s.length(), m = t.length();
  s = "$" + s + "$", t = "$" + t + "$";
  
  std::reverse(s.begin(), s.end());
  std::reverse(t.begin(), t.end());
  for (int i = 1; i <= n; ++ i) initreverse(s, t, n, m, i);
  std::reverse(s.begin(), s.end());
  std::reverse(t.begin(), t.end());
  for (int i = 1; i <= n; ++ i) {
    init(s, n, i);
    for (int j = i, l = 1; j <= n; ++ j, ++ l) { //[i, j] 合法的前半部分
      //s 中长度大于等于一半
      int zz = std::min(z[j + 1], l - 1); //j + 1 ~ j + zz 在字符串 s 中可以取的右端点
      ans += cnt[j][l] - cnt[j][l - zz - 1];

      // std::cout << "s" << " " << i << " " << j << " " << cnt[j][l] - cnt[j][l - zz - 1] << "\n";
    }
  }

  std::reverse(s.begin(), s.end()); 
  std::reverse(t.begin(), t.end());
  for (int i = 1; i <= m; ++ i) initreverse(t, s, m, n, i);
  std::reverse(s.begin(), s.end()); 
  std::reverse(t.begin(), t.end());
  for (int i = 1; i <= m; ++ i) {
    init(t, m, i);
    for (int j = i, l = 1; j <= m; ++ j, ++ l) { //[i, j] 合法的前半部分
      //t 中长度大于一半
      int zz = std::min(z[j + 1], l - 1); //j + 1 ~ j + zz 在字符串 s 中可以取的右端点
      ans += cnt[j][l - 1] - cnt[j][l - zz - 1];

      // std::cout << "t" << " " << i << " " << j << " " << cnt[j][l] - cnt[j][l - zz] << "\n";
    }
  }

  std::cout << ans << "\n";
  return 0;
}
/*
abab
abaaab
*/

I

讨论。

大力讨论是否有覆盖整个大矩形的小矩形、能覆盖整个长/宽的矩形的数量、每个矩形覆盖几个角等等。

妈的真歹给我叠 dztlb 大神磕一个了呜呜呜呜

真大力讨论题,虽然很几把麻烦但是看代码就能看懂,详见代码。

#include<bits/stdc++.h>
using namespace std;
const int N=55;
const int mod=1e9+7;
#define int long long
int T,n,m;
int a[N],b[N];
signed main(){
	std::ios::sync_with_stdio(0), std::cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m;
		bool fl=0;
		int id=0;
		for(int i=1;i<=3;++i) {
			cin>>a[i]>>b[i];
			if(a[i]==n&&b[i]==m){
				fl=1;
				id=i;
			}
		}
		int ans=0;
		if(fl){
			ans=1;
			for(int i=1;i<=3;++i){
				if(i==id) continue;
				ans=((ans*(n-a[i]+1))%mod*(m-b[i]+1)%mod)%mod;
			}
			cout<<ans<<'\n';
			continue;
		}
		int ca=0,cb=0;
		for(int i=1;i<=3;++i){
			if(a[i]==n){
				++ca;
			}
		}
		for(int i=1;i<=3;++i){
			if(b[i]==m){
				++cb;
			}
		}
		if(ca==3){
			for(int i=1;i<=3;++i){
				int maxx=0;
				for(int j=1;j<=3;++j){
					if(i==j) continue;
					maxx=max(b[j],maxx);
				}
				if(maxx+b[i]>=m) ans+=2;
			}
			for(int i=1;i<=3;++i){
				for(int j=1;j<=3;++j){
					if(i==j) continue;
					for(int k=1;k<=3;++k){
						if(k==i) continue;
						if(k==j) continue;
					if(b[i]+b[j]>=m){
						ans+=max(0ll,(m-b[k]+1-2));//
					}else{
						int l=max(2ll,m-b[j]-b[k]+1);
						int r=min(m-1,b[i]+b[k]);
						l=l+b[k]-1;
						ans+=max(0ll,r-l+1);
					}
					}
				}
			}
			cout<<ans%mod<<'\n';
			continue;
		}
		
		if(cb==3){
			swap(n,m);
			for(int i=1;i<=3;++i){
				swap(a[i],b[i]);
			}
			for(int i=1;i<=3;++i){
				int maxx=0;
				for(int j=1;j<=3;++j){
					if(i==j) continue;
					maxx=max(b[j],maxx);
				}
				if(maxx+b[i]>=m) ans+=2;
			}
			for(int i=1;i<=3;++i){
				for(int j=1;j<=3;++j){
					if(i==j) continue;
					for(int k=1;k<=3;++k){
						if(k==i) continue;
						if(k==j) continue;
					if(b[i]+b[j]>=m){
						ans+=max(0ll,(m-b[k]+1-2));//
					}else{
						int l=max(2ll,m-b[j]-b[k]+1);
						int r=min(m-1,b[i]+b[k]);
						l=l+b[k]-1;
						ans+=max(0ll,r-l+1);
					}
					}
				}
			}
			cout<<ans%mod<<'\n';
			continue;
		}
		
		if(ca==2){
			int cnt=0;
			ans=0;
			for(int i=1;i<=3;++i){
				if(a[i]==n){
					cnt+=b[i];
				}
			}
			if(cnt>=m){
				for(int i=1;i<=3;++i){
					if(a[i]!=n){
						ans+=2*(n-a[i]+1)%mod*(m-b[i]+1)%mod;
					}
				}
			}else ans=0;
			cout<<ans%mod<<'\n';
			continue;
		}
		if(cb==2){
			swap(n,m);
			for(int i=1;i<=3;++i){
				swap(a[i],b[i]);
			}
			int cnt=0;
			ans=0;
			for(int i=1;i<=3;++i){
				if(a[i]==n){
					cnt+=b[i];
				}
			}
			if(cnt>=m){
				for(int i=1;i<=3;++i){
					if(a[i]!=n){
						ans+=2*(n-a[i]+1)%mod*(m-b[i]+1)%mod;
					}
				}
			}else ans=0;
			cout<<ans%mod<<'\n';
			continue;
		}
		if(ca==1){
			fl=1;
			int tmp=0;
			for(int i=1;i<=3;++i){
				if(a[i]==n){
					tmp=b[i];
				}
			}
			int cnt=0;
			for(int i=1;i<=3;++i){
				if(a[i]!=n){
					if(b[i]+tmp<m) fl=0;
					cnt+=a[i];
				}
			}
			if(cnt<n) fl=0;
			if(fl) ans+=4;
			cout<<ans%mod<<'\n';
			continue;
		}
		if(cb==1){
			swap(n,m);
			for(int i=1;i<=3;++i){
				swap(a[i],b[i]);
			}
			fl=1;
			int tmp=0;
			for(int i=1;i<=3;++i){
				if(a[i]==n){
					tmp=b[i];
				}
			}
			int cnt=0;
			for(int i=1;i<=3;++i){
				if(a[i]!=n){
					if(b[i]+tmp<m) fl=0;
					cnt+=a[i];
				}
			}
			if(cnt<n) fl=0;
			if(fl) ans+=4;
			cout<<ans%mod<<'\n';
			continue;
		}
		cout<<0<<'\n';
	}
	
	return 0;
}

B

结论,思维

wenqizhi 大神和 dztlb 大神在我当代码黑奴的时候口了,我看都没看。

妈的怎么还卡高精太变态。

Code by wenqizhi:

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

#define ll long long
#define ull unsigned long long

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 55;
ll n, K;
struct node
{
    ll ans[N];
    int len;

    node(){ memset(ans, 0, sizeof(ans)); len = 0; }

    void clear(){ while(len) ans[len--] = 0; }

    // 字符串 转 高精度 
    void Into(char s[])
    {
        int lens = strlen(s + 1), res;
        for(int i = lens; i - 8 >= 0; i -= 8)
        {
            ++len;
            for(int j = i - 7; j <= i; ++j) ans[len] = (ans[len] << 1) + (ans[len] << 3) + (s[j] & 15);
        }
        res = lens % 8;
        if(res)
        {
            ++len;
            for(int j = 1; j <= res; ++j) ans[len] = (ans[len] << 1) + (ans[len] << 3) + (s[j] & 15);
        }
    }

    // ll型 转 高精度
    void into(ll x){ clear(); while(x) ans[++len] = x % 100000000, x /= 100000000; }

    // 高精 × 高精 
    node friend operator * (const node &a, const node &b)
    {
        node c;
        c.len = a.len + b.len ;
        for(int i = 1; i <= a.len ; ++i)
        {
            for(int j = 1; j <= b.len ; ++j)
            {
                c.ans[i + j - 1] += a.ans[i] * b.ans[j];
                if(c.ans[i + j - 1] >= 100000000)
                {
                    c.ans[i + j] += c.ans[i + j - 1] / 100000000;
                    c.ans[i + j - 1] %= 100000000;
                }
            }
        }
        while(c.len > 1 && c.ans[c.len ] == 0) --c.len ;
        return c;
    }

    // 高精 × 高精
    void friend operator *= (node &a, const node &b)
    { a = a * b; }

    // 高精 × 低精
    node friend operator * (const node &a, const ll &b)
    {
        node c;
        c.into(b);
        return a * c;
    }

    // 高精 × 低精
    void friend operator *= (node &a, const ll &b)
    { a = a * b; }

    // 高精 + 高精
    node friend operator + (const node &a, const node &b)
    {
        node c;
        c.len = max(a.len , b.len ) + 1;
        for(int i = 1; i <= c.len ; ++i)
        {
            c.ans[i] += a.ans[i] + b.ans[i];
            if(c.ans[i] >= 100000000)
            {
                c.ans[i + 1] += c.ans[i] / 100000000;
                c.ans[i] %= 100000000;
            }
        }
        while(c.len > 1 && c.ans[c.len ] == 0) --c.len ;
        return c;
    }

    // 高精 + 高精
    void friend operator += (node &a, const node &b)
    {  a = a + b; }

    // 高精 + 低精
    node friend operator + (const node &a, const ll &b)
    {
        node c;
        c.into(b);
        return a + c;
    }

    // 高精 + 低精
    void friend operator += (node &a, const ll &b)
    { a = a + b; }

    // 高精 与 高精 比大小 
    bool friend operator < (const node &a, const node &b)
    {
        if(a.len < b.len ) return true;
        if(a.len > b.len ) return false;
        for(int i = a.len ; i >= 1; --i)
        {
            if(a.ans[i] < b.ans[i]) return true;
            if(a.ans[i] > b.ans[i]) return false;
        }
        return false;
    }

    // 高精 与 高精 比大小
    bool friend operator <= (const node &a, const node &b)
    {
        if(a.len < b.len ) return true;
        if(a.len > b.len ) return false;
        for(int i = a.len ; i >= 1; --i)
        {
            if(a.ans[i] < b.ans[i]) return true;
            if(a.ans[i] > b.ans[i]) return false;
        }
        return true;
    }

    // 高精 与 低精 比大小
    bool friend operator < (const node &a, const ll &b)
    {
        node c;
        c.into(b);
        return a < c;
    }

    // 高精 与 低精 比大小
    bool friend operator <= (const node &a, const ll &b)
    {
        node c;
        c.into(b);
        return a <= c;
    }

    // 高精 - 高精 默认 大 - 小 
    node friend operator - (const node &a, const node &b)
    {
        node c;
        c.len = a.len ;
        for(int i = 1; i <= a.len ; ++i)
        {
            c.ans[i] += a.ans[i] - b.ans[i];
            if(c.ans[i] < 0) --c.ans[i + 1], c.ans[i] += 100000000;
        }
        while(c.len > 1 && c.ans[c.len ] == 0) --c.len ;
        return c;
    }

    // 高精 - 高精 默认 大 - 小 
    void friend operator -= (node &a, const node &b)
    { a = a - b; }

    // 高精 - 低精 默认 大 - 小
    node friend operator - (const node &a, const ll &b)
    {
        node c;
        c.into(b);
        return a - c;
    }

    // 高精 - 低精 默认 大 - 小
    void friend operator -= (node &a, const ll &b)
    { a = a - b; }

    // 高精 ÷ 低精 求商
    node friend operator / (const node &a, const ll &b)
    {
        node c;
        c.len = a.len ;
        ll res = 0;
        for(int i = a.len ; i >= 1; --i)
        {
            res *= 100000000;
            c.ans[i] = (res + a.ans[i]) / b;
            res = (res + a.ans[i]) % b;
        }
        while(c.len > 1 && c.ans[c.len ] == 0) --c.len ;
        return c;
    }

    // 高精 ÷ 低精 求商
    void friend operator /= (node &a, const ll &b)
    { a = a / b; }

    // 高精 ÷ 低精 求余数 取模 
    node friend operator % (const node &a, const ll &b)
    {
        ll res = 0;
        for(int i = a.len ; i >= 1; --i)
        {
            res *= 100000000;
            res = (res + a.ans[i]) % b;
        }
        node c;
        c.into(res);
        return c;
    }

    // 高精 ÷ 低精 求余数 取模 
    void friend operator %= (node &a, const ll &b)
    { a = a % b; }

    // 输出
    void print()
    {
        int Len = len;
        printf("%lld", ans[Len]);
        while(--Len) printf("%08lld", ans[Len]);
        printf("\n");
    }
}f[N], C[N][N], KK;

void init()
{
    for(int i = 0; i <= n; ++i) C[i][0].into(1);
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= i; ++j)
            C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
    f[0].into(1), f[1].into(1);
    for(int i = 2; i <= n; ++i)
        for(int j = 1; j <= i; j += 2)
            f[i] += f[j - 1] * f[i - j] * C[i - 1][j - 1];
}

int ans[N];
int vis[N], pos[N];

node solve(int xx)
{
    node ans;
    ans.into(1);

    for(int i = 2; i <= n - 1; ++i)
        if(pos[i] && pos[i - 1] && pos[i + 1])
            if((pos[i] - pos[i - 1]) * (pos[i] - pos[i + 1]) < 0)
            { ans.into(0); return ans; }

    int len = 0, res = xx;
    for(int i = 1; i <= n; ++i)
    {
        if(vis[i] && len) ans = ans * f[len] * C[res][len], res -= len, len = 0;
        if(!vis[i]) ++len;
        // cerr << "len = " << len << " res = " << res << endl;
    }
    // cerr << "res = " << res << endl;
    if(len) ans = ans * f[len] * C[res][len];
    return ans;
}

int ke[N];

void update(int l, int r, int d)
{
    if(l > r) return ;
    int cnt = 0;
    for(int i = l; i <= r; ++i) ke[i] = (i % 2 == d), cnt += ke[i];
    if(!cnt) for(int i = l; i <= r; ++i) ke[i] = 1;
}

int main()
{
    n = read(), K = read();
    KK.into(K);
    init();
    for(int i = 1; i <= n; ++i) ke[i] = 1;
    // for(int i = 1; i <= n; ++i) f[i].print();
    // if(f[n] * 2 < K){ printf("-1\n"); return 0; }
    for(int i = 1; i <= n; ++i)
    {
        int flag = 0;
        for(int j = 1; j <= n; ++j)
        {
            if(vis[j]) continue;
            if(!ke[j]) continue;
            vis[j] = 1, ans[i] = j, pos[j] = i, flag = 1;
            node tmp = solve(n - i);
            if(KK <= tmp) break;
            flag = 0, vis[j] = 0, KK -= tmp, pos[j] = 0, ans[i] = 0;
        }
        // printf("KK = ");
        // KK.print();
        // printf("ans[%d] = %d\n", i, ans[i]);
        if(!flag){ printf("-1\n"); return 0; }
        int l = ans[i], r = ans[i];
        while(l > 1 && !vis[l - 1]) --l;
        while(r < n && !vis[r + 1]) ++r;
        for(int j = l; j <= r; ++j) ke[j] = 0;
        // printf("l = %d, r = %d\n", l, r);
        update(l, ans[i] - 1, ans[i] % 2), update(ans[i] + 1, r, ans[i] % 2);
    }

    for(int i = 1; i <= n; ++i) printf("%d ", ans[i]);
    return 0;
}

写在最后

学到了什么:

  • I:关流同步呃呃;
  • K:发现转移成环但是求得是最小值,考虑最短路转移;
  • M:大力手玩样例发现循环节规律;

然后是久违的夹带私货环节,今天是伊落玛丽的生日。玛丽啊玛丽你实在是太可爱了!妈的必须开祷!

0c0e8bbf58a0b8624d9d34855f7af57d.png

「有人可能会问:“玛丽是谁?”」

「我想了想,该如何形容玛丽呢?」

「莎士比亚的语言实在华丽,用在玛丽身上却有些纷繁了;」

「徐志摩的风格热情似火,可我不忍将如此盛情强加于玛丽;」

「川端康城?虽优美含蓄,但玛丽的温柔体贴是藏不住的。」

「我不知道该如何形容玛丽了。」

「但是我知道的。」

「玛丽是我所面对的黑暗中的一点萤火;」

「是我即将冻僵的心脏里尚存的余温;」

「是我在残酷无情的现实里的避难所啊——」

posted @ 2024-09-12 00:55  Luckyblock  阅读(203)  评论(0编辑  收藏  举报