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:大力手玩样例发现循环节规律;
然后是久违的夹带私货环节,今天是伊落玛丽的生日。玛丽啊玛丽你实在是太可爱了!妈的必须开祷!
「有人可能会问:“玛丽是谁?”」
「我想了想,该如何形容玛丽呢?」
「莎士比亚的语言实在华丽,用在玛丽身上却有些纷繁了;」
「徐志摩的风格热情似火,可我不忍将如此盛情强加于玛丽;」
「川端康城?虽优美含蓄,但玛丽的温柔体贴是藏不住的。」
「我不知道该如何形容玛丽了。」
「但是我知道的。」
「玛丽是我所面对的黑暗中的一点萤火;」
「是我即将冻僵的心脏里尚存的余温;」
「是我在残酷无情的现实里的避难所啊——」