2024 ICPC Asia Chengdu Regional Contest(LJABGI)
2024 ICPC Asia Chengdu Regional Contest(LJABGI)
L. Recover Statistics
签到题 .
输出50个a , 45个b ,4个c , 1个c+1 即可.
J. Grand Prix of Ballance
模拟题.
注意开long long
A. Arrow a Row
构造题.
通过操作\(***...***\rightarrow\)>-...->>> , (长度需大于5) ,构造出题目给出的字符串.
最后三个字符一定是">>>",第一个一定是">" ,一定有"-",必要性显然 ,下面的构造方法能证明充分性. 找到最后一个"-".
先将字符串变为">-...->>>...>>"
中间的">"从前往后一个一个加入.
G. Expanding Array
位运算 , 思维题.
只用考虑相邻两个数能构造出来的数. 可以猜想能构造出来的东西会很少.可以尝试暴力枚举.但常数过大.
考虑优化一下 , 假设前面几个操作能构造出全部答案 , 我们不妨算一下时间能过的前提下最多进行几次.
可以算出是3次. 然后就过了()
auto calc=[&](int a,int x,int b) {
if (x==0) {
return a|b;
}else if (x==1) {
return a&b;
}else {
return a^b;
}
};
auto getval=[&](int x,int y,int p) {
if (p) return x;
return y;
};
auto work=[&](int x,int y) {
for (int a1=0;a1<2;++a1) {
int res1=getval(x,y,a1);
st.insert(res1);
for (int s1=0;s1<3;++s1) {
for (int a2=0;a2<2;++a2) {
int res2=calc(res1,s1,getval(x,y,a2));
st.insert(res2);
for (int s2=0;s2<3;++s2) {
for (int a3=0;a3<2;++a3) {
int res3=calc(res2,s2,getval(x,y,a3));
st.insert(res3);
}
}
}
}
}
};
考虑证明 :
proof:
由于任意一个式子只由2个数字决定 , 不妨按位考虑 , 对于某个确定的式子 , 具体的一位只由x 和 y 的取值有关.
称让某个式子的某一位为1 的,x,y的取值为一个规则. 一共有16个规则. 但对于含有x=0 且y=0的这一个规则,可以特殊处理,因为0一定能被构造出来 . 此时还剩8种规则 . 我们可以全部构造出来.0,x,y,x|y,x&y,x^y,(x|y)^x,(x|y)^y
I. Good Partitions
简单数学, 数据结构 ,
这道题本来很快想到线段树维护gcd的做法 , 但不知道为什么想可行性的时候想成区间修改了 , 虽然也能实现 , 但是没想出办法.....
First
所以这里有一个不用线段树的解法,时间复杂度\(O(n\log n)\).
很容易想到答案是将数组划分成不减段后,除去最后一段的段长度的最大公因数的因数个数 . 由于段长很小 , 每次只会改变最多两个段, 我们可用两个数组计数 , 一个记录某个因子出现次数 , 一个记录出现次数的次数 . 最后的答案就是出现次数为段数减1的次数.比较显然.
代码细节比较多 , 下面对代码实现比较困难的一些地方和坑点进行说明 .
-
对于段长度的记录 , 我们可以用
set记录断点即满足\(a[p]<a[p-1]\)的位置 , 再把1和n+1放进去,这样能保证set不会越界. -
进行插入 , 删除操作时,判断清楚.
#include <bits/stdc++.h>
using namespace std;
#define int long long
using i64 = long long;
using i128 = __int128;
using u64 = unsigned long long;
const int mod = 998244353;
const int N = 2e5 + 9;
const int iinf = 1e9;
const i64 linf = 1e18;
const int MX=2e5;
int cnt1[N],cnt2[N];
vector<int> d[N];
void Insert(int n) {
// cout<<"insert "<<n<<endl;
for (auto x: d[n]) {
cnt2[cnt1[x]]--;
cnt2[cnt1[x]+1]++;
cnt1[x]++;
}
}
void Erase(int n) {
// cout<<"erase "<<n<<endl;
for (auto x: d[n]) {
cnt2[cnt1[x]]--;
cnt2[cnt1[x]-1]++;
cnt1[x]--;
}
}
void solve() {
int n,q;cin>>n>>q;
multiset<int> L;
vector<int> a(n+1) ;
for (int i=1;i<=n;++i) cin>>a[i],cnt1[i]=cnt2[i]=0;
cnt2[0]=n;
for (int i=1;i<n;++i) {
if (a[i]>a[i+1]) L.insert(i+1);
}
L.insert(1),L.insert(n+1);
auto it=next(L.begin());
while (it!=L.end()) {
if (*it!=n+1) Insert(*it-*prev(it));
it=next(it);
}
cout<<cnt2[L.size()-2]<<"\n";
while (q--) {
int p,v;cin>>p>>v;
//p , v;
auto r=L.end(),l=L.begin();
if (p>1 and L.count(p) and v>=a[p-1]) {
L.erase(p);
r=L.upper_bound(p),l=prev(r);
if (*r!=n+1) Insert(*r-*l);
Erase(p-*l);
if (*r!=n+1) Erase(*r-p);
}
if (p<n and L.count(p+1) and a[p+1]>=v) {
L.erase(p+1);
r=L.upper_bound(p+1),l=prev(r);
if (*r!=n+1) Insert(*r-*l);
Erase(p+1-*l);
if (*r!=n+1) Erase(*r-p-1);
}
if (p>1 and v<a[p-1] and L.count(p)==0) {
r=L.upper_bound(p),l=prev(r);
if (*r!=n+1) Erase(*r-*l);
Insert(p-*l);
if (*r!=n+1) Insert(*r-p);
L.insert(p);
}
if (p<n and v>a[p+1] and L.count(p+1)==0) {
r=L.upper_bound(p+1),l=prev(r);
if (*r!=n+1) Erase(*r-*l);
Insert(p+1-*l);
if (*r!=n+1) Insert(*r-p-1);
L.insert(p+1);
}
a[p]=v;
cout<<cnt2[L.size()-2]<<"\n";
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("C:\\ACM\\code\\test.in", "r", stdin);
// freopen("C:\\ACM\\code\\test2.in", "r", stdin);
#endif
for (int i = 1; i <= MX; ++i) {
for (int j = i; j <= MX; j += i) {
d[j].push_back(i);
}
}
int Case = 1;
cin >> Case;
while (Case--) solve();
return 0;
}
注意到 若我们把索引减一 , 就可以只维护下标的因数, 会好写很多.
void solve() {
int n,q;cin>>n>>q;
set<int> L;
vector<int> a(n+1) ;
for (int i=1;i<=n;++i) cin>>a[i],cnt1[i]=cnt2[i]=0;
for (int i=1;i<n;++i) {
if (a[i]>a[i+1]) L.insert(i+1);
}
auto it=L.begin();
while (it!=L.end()) {
Insert(*it-1);
it=next(it);
}
if (L.empty()) cout<<n<<"\n";
else cout<<cnt2[L.size()]<<"\n";
while (q--) {
int p,v;cin>>p>>v;
//p , v;
if (p>1 and L.count(p) and v>=a[p-1]) {
L.erase(p);
Erase(p-1);
}
if (p<n and L.count(p+1) and a[p+1]>=v) {
L.erase(p+1);
Erase(p);
}
if (p>1 and v<a[p-1] and L.count(p)==0) {
L.insert(p);
Insert(p-1);
}
if (p<n and v>a[p+1] and L.count(p+1)==0) {
L.insert(p+1);
Insert(p);
}
a[p]=v;
if (L.empty()) cout<<n<<"\n";
else cout<<cnt2[L.size()]<<"\n";
}
}
Second
这里是线段树维护区间gcd的写法.
单点修改 , 区间查询gcd
#include <bits/stdc++.h>
using namespace std;
#define int long long
using i64 = long long;
using i128 = __int128;
using u64 = unsigned long long;
const int mod = 998244353;
const int N = 2e5 + 9;
const int iinf = 1e9;
const i64 linf = 1e18;
int mygcd(int a,int b) {
return __gcd(abs(a),abs(b));
}
template<class T>
struct Segt {
struct node {
int l,r;
T w;// gcd
T sum;
};
vector<T> w;
vector<node> t;
Segt(){}
Segt(int n) {init(n);}
Segt(vector<int> in) {
int n=in.size()-1;
w.resize(n+1);
for (int i=1;i<=n;++i) {
w[i]=in[i];
}
init(in.size()-1);
}
#define GL k<<1
#define GR k<<1|1
void init(int n) {
t.resize(4*n +1);
auto build=[&](auto self ,int l, int r,int k=1) {
if (l==r) {
t[k]={l,r,w[l],w[l]};
return ;
}
t[k]={l,r,0,0};
int mid=(l+r)/2;
self(self,l,mid,GL);
self(self,mid+1,r,GR);
pushup(k);
};
build(build,1,n);
}
void pushup(int k) {
auto pushup=[&](node& p,node& l, node &r) {
p.w=mygcd(l.w,r.w);
p.sum=l.sum+r.sum;
};
pushup(t[k],t[GL],t[GR]);
}
void add(int pos,T val,int k=1) {
if (t[k].l==t[k].r) {
t[k].w+=val;
t[k].sum+=val;
return ;
}
int mid=(t[k].l+t[k].r)/2;
if (pos<=mid) add(pos,val,GL);
else add(pos,val,GR);
pushup(k);
}
// 单点赋值, 不用管sum
void upd(int pos,T val,int k=1) {
if (t[k].l==t[k].r) {
t[k].w=val;
return ;
}
int mid=(t[k].l+t[k].r)/2;
if (pos<=mid) upd(pos,val,GL);
else upd(pos,val,GR);
pushup(k);
}
T askgcd(int l,int r,int k=1) {
if (l<=t[k].l and t[k].r<=r) return t[k].w;
int mid=(t[k].l+t[k].r)/2;
T ans=0;
if (l<=mid) ans=mygcd(ans,askgcd(l,r,GL));
if (mid<r) ans=mygcd(ans,askgcd(l,r,GR));
return ans;
}
T asksum(int l,int r,int k=1) {
if (l<=t[k].l and t[k].r<=r) return t[k].sum;
int mid=(t[k].l+t[k].r)/2;
T ans=0;
if (l<=mid) ans+=asksum(l,r,GL);
if (mid<r) ans+=asksum(l,r,GR);
return ans;
}
};
vector<int> pri;
bitset<N> not_pri;
int d[N],g[N];
void Euler(int n=2e5) {
d[1]=1;
for (int i=2;i<=n;++i){
if (!not_pri[i]) {
pri.push_back(i);
d[i]=2;
g[i]=1;
}
for (auto j:pri) {
if (i*j>n) break;
not_pri[i*j]=1;
if (i%j==0) {
g[i * j] = g[i] + 1;
d[i * j] = (g[i * j] + 1) * d[i] / (g[i] + 1);
break;
}else {
g[i*j]=1;
d[i*j]=d[i]*d[j];
}
}
}
}
void solve() {
int n,m;cin>>n>>m;
vector<int> a(n+1),b(n+1);
for (int i=1;i<=n;++i) cin>>a[i];
for (int i=2;i<=n;++i) {
if (a[i]<a[i-1]) b[i]=i-1;//索引减一
// cout<<b[i]<<"\n";
}
Segt<int> sgt(b);
d[0]=n;
cout<<d[sgt.askgcd(1,n)]<<"\n";
for (int i=1;i<=m;++i) {
int p,v;cin>>p>>v;
if (p>1 and v<a[p-1]) sgt.upd(p,p-1);
if (p<n and a[p+1]<v) sgt.upd(p+1,p);
if (p>1 and v>=a[p-1]) sgt.upd(p,0);
if (p<n and a[p+1]>=v) sgt.upd(p+1,0);
a[p]=v;
cout<<d[sgt.askgcd(1,n)]<<"\n";
a[p]=v;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("C:\\ACM\\code\\test.in", "r", stdin);
// freopen("C:\\ACM\\code\\test2.in", "r", stdin);
#endif
int Case = 1;
cin >> Case;
Euler();
while (Case--) solve();
return 0;
}
B. Athlete Welcome Ceremony
dp , 前缀和
首先dp比较显然 , 但因为有多组询问 , 直接dp显然不行.我们考虑每组询问有没有重合的部分, 发现可以先确定一种使用情况的方案数 , 再做一个前缀和就可以了.
具体地 , 我们很先计算出恰好用了\((x,y,z)\)个\((a,b,c)\)的方案数 , 然后做一个3维前缀和. 具体实现见代码, 这里简单说一下容易出错的部分.
- 一定要限制\(a+b+c=cnt\) ,故\(c<0\)就不用管了
- 以及转移时不要遗漏
#include <bits/stdc++.h>
using namespace std;
#define int long long
using i64 = long long;
using i128 = __int128;
using u64 = unsigned long long;
const int mod = 1e9+7;
const int N = 1e6 + 9;
const int iinf = 1e9;
const i64 linf = 1e18;
int dp[2][310][310][3];
int f[310][310][310];
void solve() {
int n,q;cin>>n>>q;
string s;cin>>s;
s="@"+s;
int cnt=0;
for (int i=1;i<=n;++i) {
if (s[i]=='?') cnt++;
if (i==1) {
if (s[1]=='?') {
for (int j=0;j<3;++j) {
if (i<n and s[i+1]-'a'==j) continue;
if (j==0) dp[1][1][0][0]=1;
if (j==1) dp[1][0][1][1]=1;
if (j==2) dp[1][0][0][2]=1;
}
}else {
dp[1][0][0][s[1]-'a']=1;
}
continue;
}
for (int a=0;a<=300;++a) {
for (int b=0;b<=300;++b) {
int c=cnt-a-b;
if (c<0) break;
int now=i&1,lst=now^1;
for (int j=0;j<3;++j) dp[now][a][b][j]=0;
int k=s[i]-'a';
if (s[i]!='?') {
for (int j=0;j<3;++j) {
if (k!=j) {
dp[now][a][b][k]+=dp[lst][a][b][j],dp[now][a][b][k]%=mod;
// cout<<i<<" "<<a<<" "<<b<<" "<<k<<" "<<dp[now][a][b][k]<<endl;
}
}
}else {
for (int j=0;j<3;++j) {
if (i<n and s[i+1]-'a'==j) continue;
for (int t=0;t<3;++t) {
if (j==t) continue;
if (j==0 and a) dp[now][a][b][j]+=dp[lst][a-1][b][t],dp[now][a][b][j]%=mod;
if (j==1 and b) dp[now][a][b][j]+=dp[lst][a][b-1][t],dp[now][a][b][j]%=mod;
if (j==2 and c) dp[now][a][b][j]+=dp[lst][a][b][t],dp[now][a][b][j]%=mod;
}
// cout<<i<<" "<<a<<" "<<b<<" "<<c<<" "<<j<<" "<<dp[now][a][b][j]<<endl;
}
}
}
}
}
// cout<<cnt<<"\n";
for (int a=0;a<=300;++a) {
for (int b=0;b<=300;++b) {
for (int c=0;c<=300;++c) {
if (a+b+c==cnt) {
for (int i=0;i<3;++i) {
f[a][b][c]+=dp[n&1][a][b][i];
f[a][b][c]%=mod;
}
// if (f[a][b][c]) cout<<a<<" "<<b<<" "<<c<<" "<<f[a][b][c]<<"\n";
}
}
}
}
for (int a=1;a<=300;++a) {
for (int b=0;b<=300;++b) {
for (int c=0;c<=300;++c) {
f[a][b][c]+=f[a-1][b][c];
f[a][b][c]%=mod;
}
}
}
for (int a=0;a<=300;++a) {
for (int b=1;b<=300;++b) {
for (int c=0;c<=300;++c) {
f[a][b][c]+=f[a][b-1][c];
f[a][b][c]%=mod;
}
}
}
for (int a=0;a<=300;++a) {
for (int b=0;b<=300;++b) {
for (int c=1;c<=300;++c) {
f[a][b][c]+=f[a][b][c-1];
f[a][b][c]%=mod;
}
}
}
for (int i=1;i<=q;++i) {
int x,y,z;cin>>x>>y>>z;
cout<<f[x][y][z]<<"\n";
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("C:\\ACM\\code\\test.in", "r", stdin);
// freopen("C:\\ACM\\code\\test2.in", "r", stdin);
#endif
int Case = 1;
// cin >> Case;
while (Case--) solve();
return 0;
}

浙公网安备 33010602011771号