9.8<1>题解
T1
我也忘了我考场上在干什么了,好像是在想什么最长链拓扑序之类乱七八糟的东西,所以离正解有十万八千里的距离,现在我也看不懂我当时打了个啥,反正是连了边,用了优先队列,然后WA了
正解是用线段树优化DP,但是我连DP都没想到,就别提线段树了,先想一下最裸的DP,设$f[i][j]$代表处理完前$i$个水晶,已经选择的水晶中最小的$A$值为$j$的最大摧毁个数,考虑如何转移
如果$A_i{\leq}B_i$那么他只能摧毁炸毁的最小的$A$大于$B_i$的集合炸毁,所以说
$f[i][A_i]=max(f[i-1][B_i+1],f[i-1][B_i+2],{\cdots},f[i-1][maxx])+1$
如果$A_i>B_i$那么因为$f$数组第二维是最小值,所以最小值如果被更新为$A_i$了,那之前的最小值一定是${\geq}A_i$的,所以有
$f[i][A_i]=max(f[i-1][A_i],f[i-1][A_i+1],f[i-1][A_i+2],{\cdots},f[i-1][maxx])+1$
当然了,也有可能$A_i$对集合中的最小值没有造成影响,那么就是
$f[i][j]=f[i-1][j]+1$ $j{\in}(B_i,A_i)$
我们发现对$i$的贡献只由对$i-1$的贡献转移而来,取$max$对应了区间求最值,都+1对应了区间加法,对$f[i][A_i]$的修改对应了单点修改,所以把问题移到线段树上就可以了
1 #include<algorithm> 2 #include<iostream> 3 #include<cstdio> 4 #include<queue> 5 #define maxn 100100 6 using namespace std; 7 struct node{ 8 int zuo,you,w,lan; 9 }tr[maxn*8]; 10 int n; 11 int a[maxn],b[maxn],lsh[maxn*2]; 12 void build(int fa,int l,int r) 13 { 14 tr[fa].zuo=l; tr[fa].you=r; 15 if(l==r) return ; 16 int mid=(l+r)>>1; 17 build(2*fa,l,mid); build(2*fa+1,mid+1,r); 18 } 19 void down(int fa) 20 { 21 tr[2*fa].lan+=tr[fa].lan; tr[2*fa+1].lan+=tr[fa].lan; 22 tr[2*fa].w+=tr[fa].lan; tr[2*fa+1].w+=tr[fa].lan; 23 tr[fa].lan=0; 24 } 25 void up(int fa) 26 { 27 tr[fa].w=max(tr[2*fa].w,tr[2*fa+1].w); 28 } 29 void add(int fa,int l,int r) 30 { 31 if(tr[fa].zuo>=l&&tr[fa].you<=r) {tr[fa].lan++; tr[fa].w++; return ;} 32 if(tr[fa].lan) down(fa); 33 int mid=(tr[fa].zuo+tr[fa].you)>>1; 34 if(l<=mid) add(2*fa,l,r); 35 if(r>mid) add(2*fa+1,l,r); 36 up(fa); 37 } 38 void change(int fa,int pos,int w) 39 { 40 if(tr[fa].zuo==tr[fa].you) {tr[fa].w=max(tr[fa].w,w); return ;} 41 int mid=(tr[fa].zuo+tr[fa].you)>>1; 42 if(tr[fa].lan) down(fa); 43 if(pos<=mid) change(2*fa,pos,w); 44 else change(2*fa+1,pos,w); 45 up(fa); 46 } 47 int query(int fa,int l,int r) 48 { 49 if(tr[fa].zuo>=l&&tr[fa].you<=r) return tr[fa].w; 50 int mid=(tr[fa].zuo+tr[fa].you)>>1,ans=0; 51 if(tr[fa].lan) down(fa); 52 if(l<=mid) ans=max(ans,query(2*fa,l,r)); 53 if(r>mid) ans=max(ans,query(2*fa+1,l,r)); 54 return ans; 55 } 56 int main() 57 { 58 // freopen("ex_leader2.in","r",stdin); 59 scanf("%d",&n); 60 for(int i=1;i<=n;++i) {scanf("%d%d",&a[i],&b[i]); lsh[i]=a[i]; lsh[i+n]=b[i];} 61 sort(lsh+1,lsh+2*n+1); 62 int len=unique(lsh+1,lsh+2*n+1)-lsh-1; 63 for(int i=1;i<=n;++i) 64 { 65 a[i]=lower_bound(lsh+1,lsh+len+1,a[i])-lsh; 66 b[i]=lower_bound(lsh+1,lsh+len+1,b[i])-lsh; 67 } 68 build(1,1,len); 69 for(int i=1;i<=n;++i) 70 { 71 if(a[i]<=b[i]) {int chan=query(1,b[i]+1,len)+1; change(1,a[i],chan);} 72 else {add(1,b[i]+1,a[i]); int chan=query(1,a[i]+1,len)+1; change(1,a[i],chan);} 73 } 74 printf("%d\n",tr[1].w); 75 return 0; 76 }
T2
启发式合并or线段树合并,当时不想打数据结构,所以咕到现在
T3
考场上想到了一毛一样的dp定义,最后死在了对答案的容斥上,出现本质相同的子序列,一定是有一个字母出现了第二边导致本质相同,那就找到前面第一个和当前相同的字母,然后刨掉和那个字母相同的就可以了
设$dp[i][j]$代表以第$i$个位置为结尾的长度为$j$的本质不同的串有多少个,会重复的就是以前面的相同字母结束的串,所以$dp[i][j]=dp[i-1][j]+dp[i-1][j-1]-dp[las-1][j-1]$,注意一下数组越界的问题,可能会死
1 #include<iostream> 2 #include<cstring> 3 #include<string> 4 #include<cstdio> 5 #define ll long long 6 #define mod 998244353 7 #define maxn 3030 8 using namespace std; 9 int len,cd,flag; 10 int pos[30]; 11 char s[3010]; 12 ll f[maxn][maxn]; 13 int main() 14 { 15 // freopen("coin.in","r",stdin); 16 scanf("%s%d",s+1,&len); cd=strlen(s+1); 17 for(int i=2;i<=cd;++i) 18 if(s[i]!=s[i-1]) {flag=1; break;} 19 if(!flag) {printf("1\n"); return 0;} 20 else 21 { 22 for(int i=1;i<=cd;++i) 23 { 24 if(pos[s[i]-'a']==0) f[i][1]=f[i-1][1]+1; 25 else f[i][1]=f[i-1][1]; 26 for(int j=2;j<=min(i,len);++j) 27 { 28 if(pos[s[i]-'a']!=0) 29 f[i][j]=((f[i-1][j]+f[i-1][j-1])%mod-f[pos[s[i]-'a']-1][j-1]+mod)%mod; 30 else 31 f[i][j]=(f[i-1][j]+f[i-1][j-1])%mod; 32 } 33 pos[s[i]-'a']=i; 34 } 35 } 36 printf("%lld\n",f[cd][len]); 37 return 0; 38 }