THUSC2024 简要题解
T1
数位DP,设 \(f_{i,j,0/1}\) 表示考虑到从大到小第 \(i\) 位,当前 \(j\) 二进制中为1的位抵到上界,现在与 \(l\) 大/相等的和, \(g_{i,j,0/1}\) 表示考虑到从大到小第 \(i\) 位,当前 \(j\) 二进制中为1的位抵到上界,现在与 \(l\) 大/相等的方案数,直接每次枚举子集转移即可。
复杂度 \(O(T 3^d log_n)\),题解咋是 \(4^d\) 啊/cf。
T2
考虑判定,容易想到一个DP,\(f_i\) 表示最后一位是颜色 \(i\),前面不限可以满足的最大长度,显然 \(f_{s_i}\) 为 \(\min f_{a-z}+1\)。如果所有的 \(f\) 都大于等于 \(k\) 就合法了。
不难发现每次选的如果不是最小的 \(f\) 就不会变化,所以每次选最小的 \(f\) 即可。
T3
二分答案,维护 \(f_{i,0/1}\) 表示 \(i\) 子树满足条件的情况下往上/下的最小值是多少,之后在父亲处匹配。显然选择的一定排序后向上的是一段前缀,所以直接check并且更新 \(f\) 值就行了。
T4
task 1-3:
按照task5下发文件的形式暴搜
#include<bits/stdc++.h>
using namespace std;
int a[5005],ans=1e9;
int cur[5005];
void dfs(int now,int lim){
// cout<<now<<" "<<ans<<" "<<lim<<endl;
if(now>=ans+1){
return;
}
if(a[now-1]==lim){
for(int i=0;i<now;i++)cur[i]=a[i];
ans=now-1;
return;
}
if(a[now-1]>lim)return;
for(int i=now-1;i>=0;i--){
for(int j=now-1;j>=0;j--){
a[now]=a[i]+a[j];
if(a[now]<=a[now-1])continue;
// cout<<now<<" "<<a[i]<<" "<<a[j]<<" "<<a[now]<<" "<<a[now-1]<<" "<<lim<<" "<<ans<<endl;
dfs(now+1,lim);
}
}
}
void read(int x){
printf("input(a[%d]);\n",x);
}
void print(int x){
printf("output(a[%d]);\n",x);
}
void add(int x,int y,int z){
printf("a[%d]=a[%d]+a[%d];\n",x,y,z);
}
int main(){
freopen("opt.out","w",stdout);
a[0]=1;
read(0);
print(1);
for(int i=1;i<=128;i++){
ans=1e9;
dfs(1,i);
read(0);
for(int j=1;j<=ans;j++){
bool fl=0;
for(int k=0;k<j&&!fl;k++){
for(int l=0;l<j;l++){
if(cur[j]==cur[k]+cur[l]){
add(j,k,l);
fl=1;
break;
}
}
}
}
print(ans);
}
return 0;
}
task 4:
容易发现求得相当于 \(a_0\) 乘一个数,经过观察发现那个数是 \(2^{30}\) 模 \(998244353\)。
#include<bits/stdc++.h>
using namespace std;
void read(int x){
printf("input(a[%d]);\n",x);
}
void print(int x){
printf("output(a[%d]);\n",x);
}
void add(int x,int y,int z){
printf("a[%d]=a[%d]+a[%d];\n",x,y,z);
}
int main(){
freopen("opt4.out","w",stdout);
read(0);
for(int i=1;i<=30;i++)add(0,0,0);
print(0);
return 0;
}
task 5:
区间求和,任何非暴力算法均可通过。
#include<bits/stdc++.h>
using namespace std;
int a[5005],ans=1e9;
int cur[5005];
void read(int x){
printf("input(a[%d]);\n",x);
}
int tt=0;
void print(int x){
printf("output(a[%d]);\n",x);
}
void add(int x,int y,int z){
tt++;
printf("a[%d]=a[%d]+a[%d];\n",x,y,z);
}
void init(int x,int y){
printf("a[%d]=a[%d];\n",x,y);
}
int L[5005],R[5005],id[5005];
int sl[25][10005],sr[25][10005],tot=128;
void ztree(int p,int l,int r){
L[p]=l,R[p]=r;
id[p]=++tot;
if(l==r){
init(id[p],l);
return;
}
int mid=l+r>>1;
ztree(p*2,l,mid);
ztree(p*2+1,mid+1,r);
add(id[p],id[p*2],id[p*2+1]);
}
void solve(int p,int l,int r){
if(l<=L[p]&&R[p]<=r){
add(0,0,id[p]);
return;
}
int mid=L[p]+R[p]>>1;
if(l<=mid)solve(p*2,l,r);
if(r>mid)solve(p*2+1,l,r);
}
string str;
int main(){
freopen("opt5.in","r",stdin);
freopen("opt5.out","w",stdout);
for(int i=1;i<=128;i++)read(i);
ztree(1,1,128);
for(int i=1;i<=138;i++){
getline(cin,str);
}
int cl=1e9,cr=0;
for(int i=139;i<=6460;i++){
getline(cin,str);
if(str[0]=='o'){
// cout<<cl<<" "<<cr<<" 111\n";
init(0,1e5);
solve(1,cl,cr);
print(0);
cl=1e9,cr=0;
}
else{
int len=str.length(),h=0;
for(int i=0;i<len;i++){
if(str[i]=='[')h=0;
if('0'<=str[i]&&str[i]<='9'){
h=h*10+(str[i]-'0');
}
if(str[i]==']'){
if(h==100001||h==100002)break;
if(h==0||h==100003)continue;
cl=min(cl,h);
cr=max(cr,h);
// cout<<L[h]<<" "<<R[h]<<endl;
}
}
}
}
return 0;
}
task 6:
发现下发文件的操作为线段树求和操作。并且长度小询问次数多,所以使用猫树。
#include<bits/stdc++.h>
using namespace std;
int a[5005],ans=1e9;
int cur[5005];
void read(int x){
printf("input(a[%d]);\n",x);
}
int tt=0;
void print(int x){
printf("output(a[%d]);\n",x);
}
void add(int x,int y,int z){
tt++;
printf("a[%d]=a[%d]+a[%d];\n",x,y,z);
}
void init(int x,int y){
printf("a[%d]=a[%d];\n",x,y);
}
int L[5005],R[5005];
void ztree(int p,int l,int r){
L[p]=l,R[p]=r;
if(l==r){
// init(p,l);
return;
}
int mid=l+r>>1;
ztree(p*2,l,mid);
ztree(p*2+1,mid+1,r);
}
int sl[25][10005],sr[25][10005],tot=1024;
void build(int p,int l,int r,int dep,int ty){
if(p!=1){
sl[dep][l]=l,sr[dep][r]=r;
if(ty){
for(int i=l+1;i<=r;i++){
sl[dep][i]=++tot;
add(sl[dep][i],sl[dep][i-1],i);
}
}
else{
for(int i=r-1;i>=l;i--){
sr[dep][i]=++tot;
add(sr[dep][i],sr[dep][i+1],i);
}
}
}
if(l==r)return;
int mid=l+r>>1;
build(p*2,l,mid,dep+1,0);
build(p*2+1,mid+1,r,dep+1,1);
}
void solve(int p,int l,int r,int dep){
if(l==r){
print(l);
return;
}
int mid=L[p]+R[p]>>1;
if(mid+1<=l)solve(p*2+1,l,r,dep+1);
else if(r<=mid)solve(p*2,l,r,dep+1);
else{
// cout<<l<<" "<<r<<" "<<L[p]<<" "<<R[p]<<" "<<mid<<endl;
add(0,sl[dep+1][r],sr[dep+1][l]);
print(0);
}
}
string str;
int main(){
freopen("opt6.in","r",stdin);
freopen("opt6.out","w",stdout);
for(int i=1;i<=1024;i++)read(i);
ztree(1,1,1024);
build(1,1,1024,1,0);
for(int i=1;i<=3081;i++){
getline(cin,str);
}
int cl=1e9,cr=0;
for(int i=3082;i<=396800;i++){
getline(cin,str);
if(str[0]=='o'){
// cout<<cl<<" "<<cr<<" 111\n";
solve(1,cl,cr,1);
cl=1e9,cr=0;
}
else{
int len=str.length(),h=0;
for(int i=0;i<len;i++){
if(str[i]=='[')h=0;
if('0'<=str[i]&&str[i]<='9'){
h=h*10+(str[i]-'0');
}
if(str[i]==']'){
if(h==100001||h==100002)break;
if(h==0||h==100003)continue;
cl=min(cl,L[h]);
cr=max(cr,R[h]);
// cout<<L[h]<<" "<<R[h]<<endl;
}
}
}
}
return 0;
}
task 7:
同样是区间求和,但是要求空间线性,不难想到求逆元,也就是 \(\times 998244352\)。
发现直接求快速乘每次要34次还是35次,不足以通过,但是分解为 \(2^{23} \times 7 \times 17\) 就只需要32次了,足以通过。
#include<bits/stdc++.h>
using namespace std;
void read(int x){
printf("input(a[%d]);\n",x);
}
int tt=0;
void print(int x){
printf("output(a[%d]);\n",x);
}
void add(int x,int y,int z){
tt++;
printf("a[%d]=a[%d]+a[%d];\n",x,y,z);
}
void init(int x,int y){
printf("a[%d]=a[%d];\n",x,y);
}
int L[100005],R[100005];
void ztree(int p,int l,int r){
L[p]=l,R[p]=r;
if(l==r){
return;
}
int mid=l+r>>1;
ztree(p*2,l,mid);
ztree(p*2+1,mid+1,r);
}
string str;
void mul(int t){
int q=0;
while(t){
if(t&1){
if(q)add(4097,0,4097);
else init(4097,0),q=1;
}
if(t>1)add(0,0,0);
t>>=1;
}
}
int main(){
freopen("opt7.in","r",stdin);
freopen("opt7.out","w",stdout);
for(int i=1;i<=4096;i++)read(i);
ztree(1,1,4096);
for(int i=2;i<=4096;i++)add(i,i,i-1);
for(int i=1;i<=12297;i++){
getline(cin,str);
}
int cl=1e9,cr=0,md=998244352,ss=0;
for(int i=12298;i<=69551;i++){
getline(cin,str);
if(str[0]=='o'){
ss++;
// cout<<cl<<" "<<cr<<" 111\n";
if(cl==1)print(cr);
else{
init(4097,4098);
init(0,cl-1);
mul(7);
init(0,4097);
init(4097,4098);
mul(17);
init(0,4097);
init(4097,4098);
mul(8388608);
add(4097,cr,4097);
print(4097);
}
cl=1e9,cr=0;
}
else{
int len=str.length(),h=0;
for(int i=0;i<len;i++){
if(str[i]=='[')h=0;
if('0'<=str[i]&&str[i]<='9'){
h=h*10+(str[i]-'0');
}
if(str[i]==']'){
if(h==100001||h==100002)break;
if(h==0||h==100003)continue;
cl=min(cl,L[h]);
cr=max(cr,R[h]);
// cout<<L[h]<<" "<<R[h]<<endl;
}
}
}
}
// cout<<ss;
return 0;
}
task 8:
迪利克雷前缀和,套板子。但是需要单独计算 8191 和 8192 的答案
#include<bits/stdc++.h>
using namespace std;
void read(int x){
printf("input(a[%d]);\n",x);
}
int tt=0;
void print(int x){
printf("output(a[%d]);\n",x);
}
void add(int x,int y,int z){
tt++;
printf("a[%d]=a[%d]+a[%d];\n",x,y,z);
}
void init(int x,int y){
printf("a[%d]=a[%d];\n",x,y);
}
bool ip[10005];
int main(){
freopen("opt8.out","w",stdout);
for(int i=1;i<=8190;i++)read(i);
for(int i=1;i<=8190;i++){
if(!ip[i]&&i>1){
add(i,i,1);
for(int j=i+i;j<=8190;j+=i)add(j,j,j/i),ip[j]=1;
}
print(i);
}
read(2);
add(2,1,2);
print(2);
read(1);
add(1,1,4096);
print(1);
return 0;
}
task 9:
子集求和,直接高位前缀和,没什么值得注意的。
#include<bits/stdc++.h>
using namespace std;
void read(int x){
printf("input(a[%d]);\n",x);
}
int tt=0;
void print(int x){
printf("output(a[%d]);\n",x);
}
void add(int x,int y,int z){
tt++;
printf("a[%d]=a[%d]+a[%d];\n",x,y,z);
}
void init(int x,int y){
printf("a[%d]=a[%d];\n",x,y);
}
int main(){
freopen("opt9.out","w",stdout);
for(int i=0;i<=1023;i++)read(i);
for(int i=0;i<10;i++){
for(int s=0;s<1024;s++){
if((s>>i)&1)add(s,s,s^(1<<i));
}
}
for(int i=0;i<=1023;i++)print(i);
return 0;
}
task 10:
看上去没什么特别的,只能直接优化。
发现输入的量以及输出的量很少,所以有用的操作不多。
先正着扫一遍如果知道答案的操作就用赋值代替。
倒着扫一遍,只求要知道的信息。
然后就过了。
#include<bits/stdc++.h>
using namespace std;
void read(int x){
printf("input(a[%d]);\n",x);
}
int tt=0;
void print(int x){
printf("output(a[%d]);\n",x);
}
void add(int x,int y,int z){
tt++;
printf("a[%d]=a[%d]+a[%d];\n",x,y,z);
}
void init(int x,int y){
printf("a[%d]=a[%d];\n",x,y);
}
string str[5005];
int n=4234;
bool tg[5005],out[5005];
int fl[5005];
int main(){
freopen("opt10.in","r",stdin);
freopen("opt10.out","w",stdout);
for(int i=1;i<=n;i++){
getline(cin,str[i]);
}
for(int i=11;i<=74;i++){
int len=str[i].length();
int h=0;
for(int j=0;j<len;j++){
if('0'<=str[i][j]&&str[i][j]<='9')h=h*10+(str[i][j]-'0');
}
read(h);
// cout<<h<<endl;
tg[h]=1;
}
for(int i=75;i<=4170;i++){
int a1=0,a2=0,a3=0,h=0;
int len=str[i].length();
for(int j=0;j<len;j++){
if(str[i][j]=='[')h=0;
if('0'<=str[i][j]&&str[i][j]<='9')h=h*10+(str[i][j]-'0');
if(str[i][j]==']'){
if(a1==0)a1=h;
else if(a2==0)a2=h;
else a3=h;
}
}
if(tg[a2]==0&&tg[a3]==0){
// init(a1,0);
tg[a1]=0;
fl[i]=0;
}
else if(tg[a2]==0){
tg[a1]=1;
// init(a1,a3);
fl[i]=1;
}
else if(tg[a3]==0){
tg[a1]=1;
// init(a1,a2);
fl[i]=2;
}
else{
// add(a1,a2,a3);
tg[a1]=1;
fl[i]=3;
}
// cout<<a1<<' '<<a2<<' '<<a3<<endl;
}
for(int i=4171;i<=4234;i++){
int len=str[i].length();
int h=0;
for(int j=0;j<len;j++){
if('0'<=str[i][j]&&str[i][j]<='9')h=h*10+(str[i][j]-'0');
}
out[h]=1;
// print(h);
}
for(int i=4170;i>=75;i--){
int a1=0,a2=0,a3=0,h=0;
int len=str[i].length();
for(int j=0;j<len;j++){
if(str[i][j]=='[')h=0;
if('0'<=str[i][j]&&str[i][j]<='9')h=h*10+(str[i][j]-'0');
if(str[i][j]==']'){
if(a1==0)a1=h;
else if(a2==0)a2=h;
else a3=h;
}
}
if(!out[a1]){
fl[i]=0;
continue;
}
out[a1]=0;
out[a2]=out[a3]=1;
}
for(int i=75;i<=4170;i++){
int a1=0,a2=0,a3=0,h=0;
int len=str[i].length();
for(int j=0;j<len;j++){
if(str[i][j]=='[')h=0;
if('0'<=str[i][j]&&str[i][j]<='9')h=h*10+(str[i][j]-'0');
if(str[i][j]==']'){
if(a1==0)a1=h;
else if(a2==0)a2=h;
else a3=h;
}
}
if(fl[i]==1)init(a1,a3);
if(fl[i]==2)init(a1,a2);
if(fl[i]==3)add(a1,a2,a3);
}
for(int i=4171;i<=4234;i++){
int len=str[i].length();
int h=0;
for(int j=0;j<len;j++){
if('0'<=str[i][j]&&str[i][j]<='9')h=h*10+(str[i][j]-'0');
}
print(h);
}
return 0;
}