Link:
A:
套路题结果想了好久……
排序二叉树的性质就是中序遍历单调递增
于是只考虑当前树的中序遍历的序列即可,与树的形态无关
1 #include<bits/stdc++.h> 2 #define maxn 100005 3 using namespace std; 4 typedef long long ll; 5 int n,fa[maxn],ch[maxn][2],x; 6 ll st[maxn],a[maxn]; 7 int top=0; 8 void dfs(int i){ 9 if(ch[i][0]) dfs(ch[i][0]); 10 st[++top]=a[i]; 11 if(ch[i][1]) dfs(ch[i][1]); 12 } 13 ll q[maxn]; 14 int rear=0; 15 void run(ll x){ 16 /*if(x>=q[rear]){ 17 q[++rear]=x; 18 return; 19 } 20 int l=1,r=rear,mid; 21 while(l<=r){ 22 mid=(l+r)>>1; 23 if(q[mid-1]<=x&&x<q[mid]){q[mid]=x;return;} 24 if(x<q[mid-1]) r=mid-1; 25 else l=mid+1; 26 }*/ 27 int t=upper_bound(q+1,q+rear+1,x)-q; 28 if(t==rear+1) q[++rear]=x; 29 else q[t]=x;//将刚好大于它的代替 30 } 31 void init(){ 32 scanf("%d",&n); 33 for(int i=1;i<=n;i++) scanf("%lld",&a[i]); 34 for(int i=2;i<=n;i++){ 35 scanf("%d%d",&fa[i],&x); 36 ch[fa[i]][x]=i; 37 } 38 dfs(1); 39 40 for(ll i=1;i<=top;i++) st[i]=st[i]-i; 41 42 q[0]=-10000000000; 43 for(int i=1;i<=top;i++) run(st[i]); 44 45 int ans=n-rear; 46 printf("%d",ans); 47 } 48 int main(){ 49 init(); 50 51 return 0; 52 }
将序列改成严格单调增想到最大化不变的数,但直接LIS求的是改为非严格单调增的数
一个将严格单调增问题改为非严格的套路是将数aiai替换成ai−iai−i,对转换后序列求LIS即可
(其实也可以理解为在严格单增问题中能拓展的条件为a[i]−a[k]≥i−ka[i]−a[k]≥i−k,那么也就是a[i]−i≥a[k]−ka[i]−i≥a[k]−k)
B:
长度可行性单调,对长度二分答案
发现区间[l,r][l,r]中存在kk的条件为:gcd(l...r)=min(l...r)=kgcd(l...r)=min(l...r)=k
区间最小和gcdgcd明显可以用RMQRMQ维护,但此题卡log2log2,因此只能用STST表来维护
1 #include<bits/stdc++.h> 2 #define maxn 500005 3 using namespace std; 4 int n,a[maxn],np=0,rt=0; 5 int gcd(int x,int y){ 6 if(y==0) return x; 7 else return gcd(y,x%y); 8 } 9 int minv[maxn][20],gcdd[maxn][20],logg[maxn],w[25]; 10 void ready(){ 11 w[0]=1; 12 logg[0]=-1; 13 for(int i=1;i<=20;i++) w[i]=w[i-1]*2; 14 for(int i=1;i<=maxn-5;i++) logg[i]=logg[i/2]+1; 15 for(int i=1;i<=n;i++){ 16 minv[i][0]=gcdd[i][0]=a[i]; 17 } 18 for(int i=1;i<=logg[n];i++)//加个1保个险 19 for(int j=1;j<=n;j++) 20 if(j+w[i]-1<=n){ 21 minv[j][i]=min(minv[j][i-1],minv[j+w[i-1]][i-1]);//本来是j+(1<<(i-1))-1+1 22 gcdd[j][i]=gcd(gcdd[j][i-1],gcdd[j+w[i-1]][i-1]); 23 } 24 } 25 int cd1(int i,int j){ 26 int len=j-i+1; 27 int t=logg[len]; 28 return min(minv[i][t],minv[j-w[t]+1][t]); 29 } 30 int cd2(int i,int j){ 31 int len=j-i+1; 32 int t=logg[len]; 33 return gcd(gcdd[i][t],gcdd[j-w[t]+1][t]); 34 } 35 int st[maxn],top=0,yb[maxn],topp=0; 36 bool check(int mid){ 37 topp=0; 38 bool ok=false; 39 for(int i=1;i<=n-mid+1;i++){ 40 int j=i+mid-1; 41 if(cd1(i,j)==cd2(i,j)){ 42 ok=true; 43 yb[++topp]=i; 44 } 45 } 46 if(ok){ 47 top=0; 48 for(int i=1;i<=topp;i++) st[++top]=yb[i]; 49 return true; 50 } 51 return false; 52 } 53 void run(){ 54 int A=1,B=n,mid,ans; 55 while(A<=B){ 56 mid=A+B>>1; 57 if(check(mid)) 58 ans=mid,A=mid+1; 59 else B=mid-1; 60 } 61 printf("%d %d ",top,ans-1);//L-R 62 for(int i=1;i<=top;i++) printf("%d ",st[i]); 63 } 64 void init(){ 65 scanf("%d",&n); 66 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 67 ready(); 68 run(); 69 } 70 int main(){ 71 init(); 72 73 return 0; 74 }
如果只有询问用STST表O(1)O(1)询问
同时注意由于对一个数多次求gcdgcd不会影响区间gcdgcd值,因此可以直接用STST表维护gcdgcd
C:
很像以前冒泡排序相关题的一个TrickTrick:
由于交换序列是一个排列,因此每次交换后左右不可能再有交换,这样就拆为独立的子问题了
由于初始状态的值有规律是单调的,因此反向考虑问题:
对于当前区间[l,r][l,r]枚举交换位置ii,如果交换i,i+1i,i+1后左序列是[l,l+i−1][l,l+i−1]的一个排列则计算其贡献
dp[l][r]=dp[l][l+i−1]∗dp[l+i][r]∗Ci−1r−l+1−2dp[l][r]=dp[l][l+i−1]∗dp[l+i][r]∗Cr−l+1−2i−1
1 #include<bits/stdc++.h> 2 #define maxn 55 3 using namespace std; 4 typedef long long ll; 5 const ll mod=1e9+7; 6 ll c[maxn][maxn],dp[maxn][maxn]; 7 int p[maxn],n; 8 void ready(){ 9 for(int i=0;i<=50;i++){ 10 c[i][0]=1; 11 for(int j=1;j<=i;j++) 12 c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod; 13 } 14 } 15 ll dfs(int len,int now){ 16 if(dp[len][now]!=-1) return dp[len][now]; 17 if(len==1) return dp[len][now]=1; 18 ll ans=0;int j,k,pos=0,t[maxn];//pos和t只有内定了给,不然要重 函数递归时会改变 19 for(int i=1;i<=n;i++) 20 if(p[i]>=now&&p[i]<now+len) t[++pos]=p[i];//pos==len 21 for(int i=1;i<pos;i++){ 22 swap(t[i],t[i+1]);//枚举交换的位置 23 for(j=1;j<=i;j++)//判断是否满足 24 if(t[j]>=now+i) break;//最多为now+i-1; 25 for(k=i+1;k<=pos;k++) 26 if(t[k]<now+i) break;//最小为now+i; 27 if(j>i&&k>pos){ 28 ll t1=dfs(i,now); 29 ll t2=dfs(pos-i,now+i); 30 ans=(ans+t1*t2%mod*c[pos-2][i-1]%mod)%mod;//第一个:now~now+i-1 第二个now+i~now+len-1; 31 //在这之前进行n-2次交换,前i-1次选择前面来处理前面的i个 32 } 33 swap(t[i],t[i+1]); 34 } 35 return dp[len][now]=ans; 36 } 37 void init(){ 38 scanf("%d",&n); 39 for(int i=1;i<=n;i++) scanf("%d",&p[i]); 40 memset(dp,-1,sizeof(dp)); 41 dfs(n,0); 42 if(dp[n][0]==-1) printf("%d",0); 43 else printf("%lld",dp[n][0]); 44 } 45 int main(){ 46 ready(); 47 init(); 48 49 return 0; 50 }
注意这里每次处理的[l,r][l,r]的排列是pp的子序列!
(处理到该子问题时能保证数在pp中的相对位置不变)
D:
关键在于贡献为2R+C2R+C,可以理解为对每一个子集算一次贡献
接下来算每个集合被包含的期望次数即可:
res=∑Cin∗Cjn∗Ck−numm−numCkmres=∑Cni∗Cnj∗Cm−numk−numCmk
其中numnum为如果ii行jj列全涂黑的个数,预处理组合数即可
1 #include<bits/stdc++.h> 2 #define maxn 305 3 using namespace std; 4 int n,m,k; 5 double a[maxn],b[maxn*maxn];//b:(k-x,m-x)/(k,m) 而x最多为n^2 6 void ready(){ 7 a[0]=1.0; 8 for(int i=1;i<=n;i++) a[i]=a[i-1]*(n-i+1)*1.0/i; 9 b[0]=1; 10 for(int i=1;i<=n*n;i++) b[i]=b[i-1]*(k-i+1)*1.0/(m-i+1); 11 } 12 void outt(){ 13 printf("1"); 14 for(int i=1;i<=99;i++) printf("0"); 15 } 16 int main(){ 17 scanf("%d%d%d",&n,&m,&k); 18 ready(); 19 double ans=0; 20 for(int i=0;i<=n;i++) 21 for(int j=0;j<=n;j++){ 22 int x=(i+j)*n-i*j; 23 if(x>k) break;//再加只会更多 24 ans+=a[i]*a[j]*b[x]; 25 } 26 if(ans>1e99) outt(); 27 else printf("%lf",ans); 28 29 return 0; 30 }