A 筛法预处理出素数表后进行遍历m的最大值,复杂度O(nloglogn),也可以遍历的时候判断素数复杂度O(nsqrt(n))
#include<bits/stdc++.h> using namespace std; #define int long long #define maxx 500000 int all[maxx]; int ans,n,k; signed main() { cin>>n; all[1]=1; for(int i=2;i<=maxx;++i) { if(all[i]==1)continue; for(int j=2*i;j<=maxx;j=j+i) all[j]=1; } for(int i=2;i<=maxx;++i) { if(all[i]==0&&all[i+n]==1) {cout<<i; break;} } }B 作为D的弱化,仅仅在最大值和次大值上使用优惠最优,考虑一种情况,其在这两个值外的其余值使用A优惠,那么交换A优惠和最大值/次大值,使得结果更好,B优惠也类似,因为优惠是非减的,随着价格的升高,优惠不会降低,故而在高价格处使用优惠不会使结果更糟。
#include<bits/stdc++.h> using namespace std; #define int long long #define maxx 3000000 int all[maxx]; double ans; int a,b,x,y,n,t; int cmp(int a,int b) { return a>b; } signed main() { cin>>t; for(int i=1;i<=t;++i) { cin>>n>>x>>y; a=1; b=1; for(int i=1;i<=n;++i) cin>>all[i]; sort(all+1,all+1+n,cmp); ans=0; if(n==1) { double z=all[1]*x/100.0; double q=(all[1]-min(all[1],y))*1.0; ans=min(z,q); } else { double z=all[1]*x/100.0; double q=(all[1]-min(all[1],y))*1.0; double z1=all[2]*x/100.0; double q1=(all[2]-min(all[2],y))*1.0; ans=min(z+q1,q+z1); for(int i=3;i<=n;++i) ans=ans+all[i]; } printf("%lf\n",ans); } }
C
与前几个题解不同,不使用容斥,取而代之将txt序列个数作为一维进行转移。
考虑dp,定义dp[i][j][k][x],
i:序列长度
j:当前位置元素,其中0代表t,1代表x,2代表其他
k:当前位置前一个的元素
x:是否存在txt序列
有转移方程
dp[i][j][k][x][w]+=dp[i-1][k][x][w] (如果xkj不构成txt)
dp[i][j][k][x][w]+=sum*(dp[i-1][k][x][0]+dp[i-1][k][x][1]) (如果xkj构成txt,如果j为2则sum为24,否则为1)
由于2代表其他字母,有24个,故而转移的时候要使用sum。
统计∑dp[n][j][k][1]
#include<bits/stdc++.h> using namespace std; #define int long long #define maxx 300000 #define mod 998244353 #define INF 0x3f3f3f3f int dp[maxx][3][3][2]; //t,x,其他 int all[maxx]; int ans; int a,b,x,y,n,t,l; signed main() { cin>>t; for(int i=1;i<=t;++i) { cin>>all[i]; n=max(n,all[i]); } for(int i=0;i<=n;++i) for(int j=0;j<=2;++j) for(int k=0;k<=2;++k) dp[i][j][k][0]=dp[i][j][k][1]=0; int noww; for(int j=0;j<=2;++j) for(int k=0;k<=2;++k) {noww=1; if(j==2) noww=noww*24; if(k==2) noww=noww*24; dp[2][j][k][0]=noww; } for(int i=3;i<=n;++i) for(int j=0;j<=2;++j) for(int k=0;k<=2;++k) for(int x=0;x<=2;++x) {if(j==2) noww=24; else noww=1; if(x==0&&k==1&&j==0) dp[i][j][k][1]=(dp[i][j][k][1]+dp[i-1][k][x][0]+dp[i-1][k][x][1])%mod; else {dp[i][j][k][0]=(dp[i][j][k][0]+(dp[i-1][k][x][0]*noww)%mod)%mod; dp[i][j][k][1]=(dp[i][j][k][1]+(dp[i-1][k][x][1]*noww)%mod)%mod; } } for(int i=1;i<=t;++i) { ans=0; for(int j=0;j<=2;++j) for(int k=0;k<=2;++k) ans=(ans+dp[all[i]][j][k][1])%mod; cout<<ans<<"\n"; } }
D
与现有题解贪心方法略有不同,并且补充了B使用连续的说明。
考虑贪心并且枚举所有端点,首先有一个结论,B的使用必然是连续,考虑如下证明,将商品价格自大而小排序,假设存在一个点,此点以后(包括此点),商品价格均小于y,依照这个点将区间分为两个区间,考虑这两个区间,假定左区间分到x个B使用,右区间分到y个B使用,那么左区间的B使用不论放在什么位置对最终答案都没有影响,因为减少的是定值,所以考虑连续放在左区间的右端点附近,因为如果在此区间使用A,那么这样做至少不会导致使用A出现更糟的结果,对于右区间,在右区间的左端点附近连续使用B更优,这样做后考虑剩余位置放若干个A,交换A和任意的B,设交换A和B的位置值为a和b,则需要比较pa+b和pb+a的值,这个值是单调的,故而不进行交换结果更好,结合以上两种说明,至少在临界点B的使用是连续的,推广关于临界点的论断,剩余两个均有单调性,B在这两段分别连续,结合临界点,可以得知B的使用连续。
已知B的使用连续后,考虑对B最开始使用的左端点进行枚举,而右端点的可行位置是一个与n无关的常数,能够产生可行位置的区间长度分别为:A操作最终值开始大于B操作值的临界点,0,B操作使用最大次数,n-B操作使用次数,商品价格开始小于y的临界点。
能够枚举这些右端点位置而不忽略最优解的原因是:考虑由两个临界点划分产生的三个区间,如果在这三个区间内枚举,答案是单调的,因为依照这样的划分方式,能够使得此区间内的答案不受选择A还是选择B最优,选择B会不会出现负数这两个条件的干扰,而除去这两个临界点,剩余的枚举是防止特殊情况造成的影响,包括:A,B其中有一个为0,在区间内,此时可供使用B次数过少,无法达到区间端点;其中n-B操作次数是在A+B大于n时合法枚举的保证。
基于上述的枚举模式不适用与A+B<N,因为此时不必要枚举B的使用次数,因为A与B过少,在前A+B内自然全部使用,故而要进行特殊处理。
其余细节参见代码。
#include<bits/stdc++.h> using namespace std; #define int long long #define maxx 300000 #define INF 0x3f3f3f3f int all[maxx]; int all1[maxx]; double ans; int a,b,x,y,n,t,l; int fg1,fg2; int cmp(int a,int b) { return a>b; } int cx(int i,int j) //查询[i,j]区间内的数字和 { if(i>j) return 0; return all1[j]-all1[i-1]; } int pd(int i,int l) //判断长度是否合法 { if(i+l-1<=n&&l>=n-a&&l<=b) return 1; else return 0; } void solve(int i,int l) //枚举区间起点和长度后进行计算 { if(l<0) return ; //不合法 int aa=cx(i,i+l-1); //使用B区间长度 double ans1=0; //判断是否出现小于y临界点 if(fg2>=i&&fg2<=i+l-1) aa=aa-(fg2-i+1)*y-cx(fg2+1,i+l-1); else if(fg2<i) aa=0; else aa=aa-l*y; //剩余部分用A if(a+b>=n) ans1=(cx(1,n)-cx(i,i+l-1))*x/100.0+aa; //对于A+B<N else {ans1=(cx(1,a+b)-cx(i,i+l-1))*x/100.0+aa; ans1=ans1+cx(a+b+1,n); } ans=min(ans,ans1); } signed main() { cin>>t; for(int i=1;i<=t;++i) { cin>>n>>a>>b>>x>>y; for(int i=1;i<=n;++i) cin>>all[i]; sort(all+1,all+1+n,cmp); for(int i=1;i<=n;++i) all1[i]=all1[i-1]+all[i]; fg1=n+1; fg2=n+1; //临界点1 for(int i=1;i<=n;++i) { if(all[i]*x>(all[i]-y)*100) { fg1=i-1; break; } } //临界点2 for(int i=1;i<=n;++i) { if(all[i]<y) { fg2=i-1; break; } } ans=INF*1.0; for(int i=1;i<=n;++i) { if(a+b<n) { solve(i,b); if(i>a) break; continue; } if(pd(i,0)) solve(i,0); if(pd(i,b)) solve(i,b); if(pd(i,n-a)) solve(i,n-a); if(pd(i,fg1-i+1)) solve(i,fg1-i+1); if(pd(i,fg2-i+1)) solve(i,fg2-i+1); if(pd(i,n-i+1)) solve(i,n-i+1); } printf("%lf\n",ans); } }
E
变形背包
与前一个题解略有区别,未使用复杂数据结构,如线段树等。
根据题意的叙述,引入了最多花费金钱,容易想到背包问题,但在这道题目,简单的背包设计无法避免后效性,如当处理到第i个元素,由于每次消除的总元素是偶数,所以到第i个元素后,如果选择消除,无法判断1.是否是合法的消除 2.和谁消除。
针对1,修改转移方式,每次每次利用它自身和后一个元素进行转移,如此转移一定是偶数。
针对2,依照1的转移方式后,容易使人联想,一定是当前元素和其后一个元素被消除吗?事实上不一定是这样,这里涉及到两个消除段的合并,如果转移到的状态的末尾段的末尾元素是当前元素前一个元素,那么这两个消除段可以进行合并,并且这样的合并一定是合法的,
因为完全可以构造一种消除方式,每次消除前一段的开头和后一段末尾,否则不可以,同时合并两个消除段,增加的价值仅仅是前一个元素和前两个元素之间的价值,这就说明了不必末尾记录消除段长。
为了判断末尾段元素是否是当前元素的前一个元素,状态内引入一维,用于判别当前位置是否被消除。
dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]); //未选择,直接转移 //如果j足够大 if(j>=all[all2[i]]+all[all2[i-1]]) //如果不可以合并,就直接转移,否则合并两段 dp[i][j][1]=max(dp[i-2][j-all[all2[i]]-all[all2[i-1]]][0]+cx(all2[i-1],all2[i]),dp[i-2][j-all[all2[i]]-all[all2[i-1]]][1]+cx(all2[i-2],all2[i]));
另外有一些细节,在消除段的时候,由于被消除的是2,只用遍历2的数组;预处理出消除位置除了2的前缀和,方便查询消除段的价值和;价值和最大为5000*10^9,如果要标记未定义,至少标记到1e13;注意初始状态的设置。
复杂度O(nm)
#include<bits/stdc++.h> using namespace std; #define int long long #define maxx 300 #define mod 998244353 #define INF 0x3f3f3f3f int dp[maxx][maxx][2]; // int all[maxx]; int all2[maxx]; int qz[maxx]; int ans; int a,b,x,y,n,t,l,k,neww1,neww2; int cx(int i,int j) //查询段长 { return qz[j]-qz[i-1]; } signed main() { cin>>t; for(int i=1;i<=t;++i) { cin>>n>>k; neww1=neww2=0; for(int i=1;i<=n;++i) { cin>>x>>all[i]; qz[i]=qz[i-1]; if(x==2) all2[++neww2]=i; else qz[i]=qz[i]+all[i]; } for(int j=0;j<=k;++j) {dp[2][j][0]=dp[2][j][1]=-INF; dp[1][j][0]=dp[1][j][1]=-INF; } dp[1][0][0]=0; dp[2][0][0]=0; dp[2][all[all2[1]]+all[all2[2]]][1]=cx(all2[1],all2[2]); for(int i=3;i<=neww2;++i) for(int j=0;j<=k;++j) { dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]); if(j>=all[all2[i]]+all[all2[i-1]]) dp[i][j][1]=max(dp[i-2][j-all[all2[i]]-all[all2[i-1]]][0]+cx(all2[i-1],all2[i]),dp[i-2][j-all[all2[i]]-all[all2[i-1]]][1]+cx(all2[i-2],all2[i])); } ans=0; for(int j=0;j<=k;++j) for(int w=0;w<=1;++w) ans=max(ans,dp[neww2][j][w]); cout<<ans<<"\n"; } }
F
题解写的挺清楚的,除了题解外也没想到什么更简便的构造方法。