3.29省选练习
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
如果现在离省选只剩最后一周,现在的我是否具有进队的能力$?$ 显然是没有吧,如今省选确实推迟了,但是我又能提高到省队水平吗,貌似也不太可能 诶,还是说,我们可能现在会说,如果我早学一年,我就... 嗯$?$都到现在了,说这个确实很无力,不如把每一天都充实的度过,即使省选前最后一天,也是在进步的过程 有了这样的心态,最后的结果是什么,我都能欣然接受,即使可能会失去很多东西,胡思乱想是没有的啊,一直努力就好了$!$
$T1$
设$dp[i][j]$表示我们使用了$j$张牌,组成$i$的方案数
转移比较寄,我看了半天没看懂(当我发现题解里面没写差分我特别想把这个人拉出来打一顿)...
$dp[n][k]=C_4^0f(n-k,k)+C_4^1(n-k,k-1)+C_4^2(n-k,k-2)+C_4^3(n-k,k-3)+C_4^4(n-k,k-4)$
这个就是一个类似差分的过程
我们这里就类似与枚举选第几个数字的过程了
上面的转移式子大概是$:$在上次选择数字的基础上,我们将一些数字加$1$的过程
我们枚举多少个数字是$1,$这样每次往后推$n$次的时候,就相当于选出来的最大值是$n$了
大概每次往后推都是整体加$1$的过程,然后我们选择这一步是否要新加一张$1$
现在转移比较显然了,那么考虑矩阵加速即可
感觉很像差分却又不是差分,只是把选数的过程倒过来了,最后还是枚举选哪些数,并且保证每种情况都考虑到了,这种转移思想确实很强,这种选数方式就相当于,用不同的方式累加出原数的过程
这道题弱化版是可以$FFT$的,毕竟在没看数据范围之前,我直接想到了生成函数
#define Eternal_Battle ZXK #include<bits/stdc++.h> #define int long long #define mod 1000000009 using namespace std; int C4[10]={1,4,6,4,1}; int f[20][20]; struct Mat { int a[110][110]; Mat(){memset(a,0,sizeof(a));} int* operator [] (int x) { return a[x]; } }A,B,bas; Mat operator * (Mat a,Mat b) { Mat c; for(int k=1;k<=100;k++) { for(int i=1;i<=100;i++) { for(int j=1;j<=100;j++) { c[i][j]=(c[i][j]+1ll*a[i][k]*b[k][j])%mod; } } } return c; } Mat my_pow(Mat x,int b) { Mat res; for(int i=1;i<=100;i++) { res[i][i]=1; } while(b) { if(b&1) { res=res*x; } x=x*x; b>>=1; } return res; } int n,k; void Init() { f[0][0]=1; for(int i=1;i<=10;i++) { for(int j=1;j<=i;j++) { for(int k=0;k<=min(j,4ll);k++) { f[i][j]+=f[i-j][j-k]*C4[k]; } } } } signed main() { Init(); int cnt=0; for(int i=10;i>=1;i--) { for(int j=10;j>=1;j--) { A[1][++cnt]=f[i][j]; } } for(int i=1;i<=10;i++) { for(int j=0;j<=min(i-1,4ll);j++) { bas[i*10-(i-j)+1][10-i+1]=C4[j]; } } for(int i=11;i<=100;i++) { bas[i-10][i]=1; } while(1) { cin>>n>>k; if(n==k&&k==0) return 0; int ans=0; if(n<=10) { for(int i=1;i<=k;i++) { ans+=f[n][i]; } cout<<ans<<"\n"; } else { B=A*my_pow(bas,n-10); for(int i=1;i<=k;i++) { ans=(ans+B[1][10-i+1])%mod; } cout<<ans<<"\n"; } } }
$T2$
给定一些三维平面,询问每次能摧毁的包含被摧毁点的平面,找最小$z$
三维上包含一个点,显然暴力$KD-Tree$写就好了
比较直接的思想是,点找面,这样的话单次需要遍历一下树,显然不是很优
那么反过来,面找点,每个点只能匹配一个,又由于我们每次优先选$z$较小的,那么就排序
对点建树,那么对于面进行查询,优先找编号小的点然后删去,然后跑一遍匹配就好了
#define Eternal_Battle ZXK #include<bits/stdc++.h> #define INF 100000000 #define MAXN 100005 #define ls tr[x].lson #define rs tr[x].rson using namespace std; int n,m,rt,cnt,nowk,Ans1; int fa[MAXN],pos[MAXN],Ans[MAXN]; struct Mian { int xl,yl,xr,yr,z,id; friend bool operator <(const Mian &a, const Mian &b) { return a.z<b.z; } }B[MAXN]; struct Poi { int x,y,id; friend bool operator <(const Poi &a, const Poi &b) { return nowk?a.x<b.x:a.y<b.y; } }S[MAXN]; struct KDTREE { int mn,id,x,y; int minx,miny,maxx,maxy; int lson,rson; }tr[MAXN]; void pushup(int x) { tr[x].mn=tr[x].id; if(ls) { tr[x].mn=min(tr[x].mn,tr[ls].mn); tr[x].minx=min(tr[x].minx,tr[ls].minx); tr[x].miny=min(tr[x].miny,tr[ls].miny); tr[x].maxx=max(tr[x].maxx,tr[ls].maxx); tr[x].maxy=max(tr[x].maxy,tr[ls].maxy); } if(rs) { tr[x].mn=min(tr[x].mn,tr[rs].mn); tr[x].minx=min(tr[x].minx,tr[rs].minx); tr[x].miny=min(tr[x].miny,tr[rs].miny); tr[x].maxx=max(tr[x].maxx,tr[rs].maxx); tr[x].maxy=max(tr[x].maxy,tr[rs].maxy); } } void build(int &x,int f,int l,int r,int KD) { if(l>r)return; int mid=(l+r)>>1; x=++cnt; nowk=KD; nth_element(S+l,S+mid,S+r+1); pos[S[mid].id]=x; fa[x]=f; tr[x].id=tr[x].mn = S[mid].id; tr[x].x=tr[x].minx=tr[x].maxx=S[mid].x; tr[x].y=tr[x].miny=tr[x].maxy=S[mid].y; build(ls,x,l,mid-1,KD^1); build(rs,x,mid+1,r,KD^1); pushup(x); } void query(int x,int xl,int yl,int xr,int yr) { if(tr[x].maxx<xl||xr<tr[x].minx||tr[x].maxy<yl||yr<tr[x].miny)return; if(xl<=tr[x].minx&&tr[x].maxx<=xr&&yl<=tr[x].miny&&tr[x].maxy<=yr) { Ans1=min(Ans1,tr[x].mn); return; } if(tr[x].id!=INF&&xl<=tr[x].x&&tr[x].x<=xr&&yl<=tr[x].y&&tr[x].y<=yr) { Ans1=min(Ans1,tr[x].id); } if(ls)query(ls,xl,yl,xr,yr); if(rs)query(rs,xl,yl,xr,yr); } void Del(int x) { tr[x].id=INF; while(x) { pushup(x); x=fa[x]; } } int main() { scanf("%d",&n); for(int i=1,xl,xr,yl,yr,z;i<=n;++i) { scanf("%d%d%d%d%d",&xl,&xr,&yl,&yr,&z); B[i]=(Mian){xl,yl,xr,yr,z,i}; } scanf("%d",&m); for(int i=1;i<=m;++i) { scanf("%d%d",&S[i].x,&S[i].y),S[i].id=i; } build(rt,0,1,m,0); sort(B+1,B+1+n); for(int i=1;i<=n;++i) { Ans1=INF; query(rt,B[i].xl,B[i].yl,B[i].xr,B[i].yr); if(Ans1!=INF) { Ans[Ans1]=B[i].id; Del(pos[Ans1]); } } for(int i=1;i<=m;++i)printf("%d\n",Ans[i]); return 0; }
$T3$
暴力$dp$较为显然
$dp[i][j][k]=\max(dp[i-1][j][k],dp[i-1][j-1][k]+u_i,dp[i-1][j][k-1]+p_i,dp[i-1][j-1][k-1]+(1-(1-u_i)(1-p_i)))$
目前这道题有$O(n^2logn)$保证正确的官方算法,和三天前(2022.3.26)刚刚被$hack$的不知道是精度问题还是别的问题出错的$wqs$二分
但是这道题被去年的学长拿出来造的都是$wqs$二分复杂度下的数据...
较为套路的是,如果存在个数限制,一般使用$wqs$二分消除限制
$dp[i][j]$表示前$i$个用了$j$个超级球和若干精灵球的最大期望,为了少用精灵球,我们加上耗费代价
那么我们目前最优解恰好用了$k$个,我们只需要加回去就好了,我们就是相当于二分斜率,然后找切点嘛~
好,套路的把第二维优化掉,然后就好了
机房讨论了一下被$hack$的原因,可能是存在一段斜率为$0$的函数导致二分出错,不过还是能过考试数据的,当然也有可能数据造假了...
#define Eternal_Battle ZXK #include<bits/stdc++.h> #define INF 0x3f3f3f3f #define MAXN 2010 using namespace std; const double eps=1e-9; int rd() { int x=0,f=1; int ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } int n,a,b,cnt1[MAXN],cnt2[MAXN];; double p[MAXN],u[MAXN],f[2010]; bool Check(double v1,double v2) { memset(f,0,sizeof(f)); memset(cnt1,0,sizeof(cnt1)); memset(cnt2,0,sizeof(cnt2)); for(int i=1;i<=n;i++) { f[i]=f[i-1]; cnt1[i]=cnt1[i-1]; cnt2[i]=cnt2[i-1]; if(f[i-1]+p[i]-v1-eps>f[i]) { f[i]=f[i-1]+p[i]-v1; cnt1[i]=cnt1[i-1]+1; cnt2[i]=cnt2[i-1]; } if(f[i-1]+u[i]-v2-eps>f[i]) { f[i]=f[i-1]+u[i]-v2; cnt1[i]=cnt1[i-1]; cnt2[i]=cnt2[i-1]+1; } if(f[i-1]+p[i]+u[i]-p[i]*u[i]-v1-v2-eps>f[i]) { f[i]=f[i-1]+p[i]+u[i]-p[i]*u[i]-v1-v2; cnt1[i]=cnt1[i-1]+1; cnt2[i]=cnt2[i-1]+1; } } return cnt2[n]<=b; } double tl; bool check(double md) { double l=0,r=1; for(int i=1;i<=50;i++) { double mid=(l+r)/2; if(Check(md,mid)) r=mid; else l=mid; } tl=l; return cnt1[n]<=a; } int main() { // freopen("c.in","r",stdin); // freopen("c.out","w",stdout); n=rd(),a=rd(),b=rd(); for(int i=1;i<=n;i++) scanf("%lf",&p[i]); for(int i=1;i<=n;i++) scanf("%lf",&u[i]); double l=0,r=1; for(int i=1;i<=50;i++) { double mid=(l+r)/2; if(check(mid)) r=mid; else l=mid; } printf("%.8f\n",f[n]+l*a+tl*b); return 0; }