2019 计蒜之道 复赛
前言
貌似gou到了一件衣服(小声bibi)
一年前:相同的6.17 计蒜之道2018一年的变化可真大,然鹅我还是那么菜
233333
___
初赛xjb AK了一场就进了复赛(其实人均AK,只是手速快一点)
大概打的是第3场
看吧,人均AK
然后冒着上学迟到的风险打了复赛
打开题目,WTF 4小时7题,开第一题 A https://nanti.jisuanke.com/t/39611
emmmmm……MD什么鬼畜题面
就是先把最长上升子序列的个数求出来,就是q
然后对于每个位置i 求经过i的最长上升子序列的个数p,然后输出p/q
第一眼XJB线段树
开一颗权值线段树,维护最大值和最大值的个数。
设f[i]为以i结尾的最长上升子序列长度,ff[i]为以i结尾的最长上升子序列的个数
然后线段树优化转移
求出最长上升子序列的个数设为q,最长上升子序列长度设为k
然后从后往前跑一边最长下降子序列
同理设为g[i]和gg[i]
然后如果f[i]+g[i] == k + 1,那进过i点的最长上升子序列个数就是ff[i]*gg[i]个
最后就……没了
时间复杂度 O(n log n)
写完交上去
WA
一整凉意心头来
切回去看看好多人切了D题
什么水题
码码码 T一发之后就切了
回到A题
经过一发思索,发现
我汤姆(WTM)没膜
%一发后就A了
考场代码:
#include<bits/stdc++.h>
#define ls rt<<1
#define rs rt<<1|1
#define int long long
#define mod 998244353
#define N 4000005
using namespace std;
int ma[N], ma_[N], f[N], ff[N], a[N], b[N], g[N], gg[N], n;
void update(int rt){//ma 表示最长上升子序列的长度(最大值) ma_表示最长上升子序列个数(最大值的个数)
if(ma[ls] > ma[rs]) ma[rt] = ma[ls], ma_[rt] = ma_[ls];
if(ma[ls] < ma[rs]) ma[rt] = ma[rs], ma_[rt] = ma_[rs];
if(ma[ls] == ma[rs]) ma[rt] = ma[ls], ma_[rt] = ma_[ls] + ma_[rs];
ma_[rt] %= mod;
}
void build(int l, int r, int rt){
if(l == r) return;
ma[rt] = ma_[rt] = 0;
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
update(rt);
}
void insert(int l, int r, int x, int o1, int o2, int rt){
if(l == r){
if(ma[rt] == o1) ma_[rt] += o2; else ma_[rt] = o2;
ma_[rt] %= mod;
ma[rt] = o1;
return;
}
int mid = (l + r) >> 1;
if(x <= mid) insert(l, mid, x, o1, o2, ls);
else insert(mid + 1, r, x, o1, o2, rs);
update(rt);
}
int query1(int l, int r, int L, int R, int rt){//求最大值
if(L <= l && r <= R) return ma[rt];
int mid = (l + r) >> 1, ret = 0;
if(L <= mid) ret = query1(l, mid, L, R, ls);
if(R > mid) ret = max(ret, query1(mid + 1, r, L, R, rs));
return ret;
}
int query2(int l, int r, int L, int R, int rt, int ha){//求最大值的个数
if(L <= l && r <= R){
if(ma[rt] == ha) return ma_[rt];//注意要等于最大值才能返回
return 0;
}
int mid = (l + r) >> 1, ret = 0;
if(L <= mid) ret = query2(l, mid, L, R, ls, ha);
if(R > mid) ret += query2(mid + 1, r, L, R, rs, ha), ret %= mod;
return ret;
}
int qpow(int y, int x){//卡速米
int t = y, ans = 1;
for(;x; x >>= 1, t = t * t % mod) if(x & 1) ans = ans * t % mod;
return ans;
}
signed main(){
scanf("%lld", &n);
for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]), b[i] = a[i];
sort(b + 1, b + 1 + n);
for(int i = 1; i <= n; i ++) a[i] = lower_bound(b + 1, b + 1 + n, a[i]) - b; //离散化
//for(int i = 1; i <= n; i ++) printf("%lld ", a[i]); printf("\n");
build(0, n, 1);
for(int i = 1; i <= n; i ++){
f[i] = query1(0, n, 0, a[i] - 1, 1) + 1;//求小于a[i]当中f值最大的数 + 1就是以i结尾的最长上升子序列
ff[i] = query2(0, n, 0, a[i] - 1, 1, f[i] - 1);//同上,最大值的个数就是以i结尾的最长上升子序列的个数
ff[i] = max(ff[i], 1ll);//注意长度和个数至少为1
insert(0, n, a[i], f[i], ff[i], 1);//插入♂
}
// for(int i = 1; i <= n; i ++) printf("%lld ", f[i]); printf("\n"); 考场上的xjb调试
// for(int i = 1; i <= n; i ++) printf("%lld ", ff[i]); printf("\n");
int qq = qpow(ma_[1], mod - 2), pp = ma[1];
// printf("**%lld** ", qq);
memset(ma, 0, sizeof ma);
memset(ma_, 0, sizeof ma_);
build(0, n + 1, 1);
for(int i = n; i >= 1; i --){
g[i] = query1(0, n + 1, a[i] + 1, n + 1, 1) + 1;//同上注意边界
gg[i] = query2(0, n + 1, a[i] + 1, n + 1, 1, g[i] - 1);
gg[i] = max(gg[i], 1ll);
insert(0, n + 1, a[i], g[i], gg[i], 1);
}
// for(int i = 1; i <= n; i ++) printf("%lld ", g[i]); printf("\n");
// for(int i = 1; i <= n; i ++) printf("%lld ", gg[i]); printf("\n");
for(int i = 1; i <= n; i ++){
if(f[i] + g[i] - 1 == pp) printf("%lld ", ff[i] * gg[i] % mod * qq % mod); else printf("0 ");//xjb输出
}
return 0;
}
然后一个半小时过去了
官方题解
看见没,和我的一样23333333
D题 “星云系统” https://nanti.jisuanke.com/t/39614
什么鬼畜名字
这题面……我给满分!
就是对于26个字母每个字母把它出现的位置记下来然后贪心就完了
听说叫做序列自动机
一开始用二分T了一波,改一下就A了
时间复杂度 O(26n)
#include<bits/stdc++.h>
using namespace std;
int n, len, pos[28];
vector<int> a[28];
char st[5000005];
int find(int x, int y){//因为位置只增不减,所以每个字母维护一个 pos就可以了
while(a[x][pos[x]] < y && pos[x] <= a[x].size() - 1){
pos[x] ++;
}
return a[x][pos[x]];
}
void dfs(int x, int y){
if(!y) return;
for(int i = 0; i < 26; i ++){//贪心找
int k = find(i, x);
if(k >= x && len - k >= y - 1){//如果能塞就塞
printf("%c", char(i + 'a'));
dfs(k + 1, y - 1);
return;
}
}
}
int main(){
for(int i = 0; i < 26; i ++) a[i].push_back(0);
scanf("%s", st + 1);
len = strlen(st + 1);
for(int i = 1; i <= len; i ++){
a[st[i] - 'a'].push_back(i);//把每个字母出现的位置记下来
}
scanf("%d", &n);
dfs(1, n);
return 0;
}
是不是很短,是不是很简单23333
官方题解
妙啊!
然后刚了一个小时的Bhttps://nanti.jisuanke.com/t/39612
题面自己去看吧,不难理解
这题就是贪心
枚举加哪张牌,然后在枚举哪个做对子
主要烦的是那些出现次数>=3的牌,要不要作为刻子
尝试了很多种贪心
后来一怒之下二进制枚举
然后就A 了
23333
时间复杂度 O(1) 都是常数,没毛病
考场代码: 不解释了,又不难,为了避免阅读代码时的不适我把考场的调试删了
#include<bits/stdc++.h>
using namespace std;
int c[55], a[55], b[55], A[55], dd[55];
int check(){
for(int pos = 1; pos <= 34; pos ++) if(a[pos] >= 2){
for(int i = 1; i <= 34; i ++) A[i] = a[i];
A[pos] -= 2;
int sz = 0;
for(int i = 1; i <= 34; i ++) if(A[i] >= 3) dd[++ sz] = i;
for(int S = 0; S < (1 << sz); S ++){
for(int i = 1; i <= 34; i ++) c[i] = A[i];
for(int i = 1; i <= sz; i ++)
if((1 << (i - 1)) & S) c[dd[i]] -= 3;
for(int i = 1; i <= 7; i ++)
if(c[i + 1] >= c[i] && c[i + 2] >= c[i]) c[i + 1] -= c[i], c[i + 2] -= c[i], c[i] = 0;
for(int i = 10; i <= 16; i ++)
if(c[i + 1] >= c[i] && c[i + 2] >= c[i]) c[i + 1] -= c[i], c[i + 2] -= c[i], c[i] = 0;
for(int i = 19; i <= 25; i ++)
if(c[i + 1] >= c[i] && c[i + 2] >= c[i]) c[i + 1] -= c[i], c[i + 2] -= c[i], c[i] = 0;
int f = 0;
for(int i = 1; i <= 34; i ++) if(c[i]) f = 1;
if(!f) return 1;
}
}
return 0;
}
int x;
char cc;
int main(){
while(~scanf("%d%c", &x, &cc)){
memset(b, 0, sizeof b);
memset(a, 0, sizeof a);
memset(dd, 0, sizeof dd);
memset(c, 0, sizeof c);
memset(A, 0, sizeof A);
if(cc == 'm') b[x] ++;
if(cc == 's') b[x + 9] ++;
if(cc == 'p') b[x + 9 + 9] ++;
if(cc == 'z') b[x + 9 + 9 + 9] ++;
for(int i = 2; i <= 13; i ++){
scanf(" %d%c", &x, &cc);
if(cc == 'm') b[x] ++;
if(cc == 's') b[x + 9] ++;
if(cc == 'p') b[x + 9 + 9] ++;
if(cc == 'z') b[x + 9 + 9 + 9] ++;
}
for(int i = 1; i <= 34; i ++) if(b[i] < 4){
for(int j = 1; j <= 34; j ++) a[j] = b[j];
a[i] ++;
if(check()){
if(i <= 9) printf("%dm\n", i);
else
if(i <= 18) printf("%ds\n", i - 9);
else
if(i <= 27) printf("%dp\n", i - 18);
else printf("%dz\n", i - 27);
}
}
}
return 0;
}
诡异的运行时间
官方题解
看了看E题
一看钟,快五点了
赶紧拎着书包就去上学了
咕咕咕咕
其他题:待填坑~
E 撑起信息安全“保护伞” https://nanti.jisuanke.com/t/39615
WTM 暴力
不解释了,看代码
code:
#include<bits/stdc++.h>
using namespace std;
int n;
char st[1000005], stt[1000005];
int check(){
int ret = 0;
for(int i = 1; i <= n; i ++){
if(stt[i] == '(') ret ++;
else ret --;
if(ret < 0) return 0;
}
return !ret;
}
int main(){
scanf("%s", st + 1);
n = strlen(st + 1);
for(int i = 1; i <= n; i ++) stt[i] = st[i];
while(1){
prev_permutation(stt + 1, stt + 1 + n);
if(check()) break;
}
for(int i = 1; i <= n; i ++) printf("%c", stt[i]); printf("\n");
for(int i = 1; i <= n; i ++) stt[i] = st[i];
while(1){
next_permutation(stt + 1, stt + 1 + n);
if(check()) break;
}
for(int i = 1; i <= n; i ++) printf("%c", stt[i]); printf("\n");
return 0;
}
官方题解
暴力你就win了,不过这个也挺有意思的
update:
收到衣服啦