【洛谷】【洛谷月赛】4月月赛Round 1/2
洛谷月赛“月”来“月”丧了,一月更比一月丧,做得我十分不“月”……
4月的两轮月赛,都只会T1,就写一下吧,等待后续更新……
先看看Round1的T1:
【R1T1】
网址:点我
【题意简述】
给定一个长度为n的序列,其中的元素均是1~m之间的正整数。
要求从中选出k个数,交换它们的位置,其他未被选中的数保持不变,使得变换后的序列中,相等的数总是排在一段连续区间。
要求最小化k。
1<=n<=105,1<=m<=20
【思路】
①想到枚举这n个数的全排列,对每个满足条件的全排列进行计算,更新答案。dfs的中途可以把已经不满足的递归树切掉,优化一下。时间复杂度O(n!*n),期望得分20。
②发现因为最后的序列一定是m个连续的相同段组成,考虑枚举m的全排列,这样可以保证答案合法,再统计。时间复杂度O(m!*n),期望得分40。
③对于②算法,可以用m个桶记录下原序列的前缀和,记sum[i][j]为序列1~i位中数j的个数,则一段区间[l,r]内不是j的个数为sum[r][j]-sum[l-1][j],把最后的统计优化到m。时间复杂度O(m!*m),期望得分70。
④考虑对③进行优化,发现有特殊的最优子结构性质。在③中,针对两个m的全排列,如果它们的前i位的数相同,但是可以有不同的顺序。
例如:1,5,3,2,4和2,5,1,3,4,它们的前4位数相同,但是顺序不一定要相同。它们的前4位在原序列中的长度相同。对于第5位,没有必要枚举所有的前四位的全排列,只需要在数相同的全排列中寻找最小值即可。
即对于一个k最优的解,它对应的m的全排列是P,P的前i位记作Pi。必然有Pi是所有Pi的全排列中的最优解。即最优子结构性质。
考虑进行状压dp,用f[S]表示集合S的全排列对应到原序列的前面若干位中的最优解。S只可能包含1~m之间的正整数。
则f[S]=min( f[S-k] + (sum[r(S)][k]-sum[r(S-k)][k]) ) k∈S。sum数组即③中sum数组,r(S)表示集合S(表示一个m的排列)对应到原序列中的长度。
时间复杂度O(2m*m),期望得分100。
【代码】
1 #include<cstdio> 2 #define F(i,a,b) for(int i=a;i<=b;++i) 3 #define F2(i,a,b) for(int i=a;i<b;++i) 4 int n,m,num[20],sum[100001][20],f[1<<20]; 5 inline int Max(int p,int q){return p>q?p:q;} 6 void init(){ 7 int x; 8 scanf("%d%d",&n,&m); 9 F(i,1,n) scanf("%d",&x),sum[i][x-1]=1,++num[x-1]; 10 F2(j,0,m) F(i,1,n) sum[i][j]+=sum[i-1][j]; 11 } 12 int main(){ 13 init(); 14 int s; 15 F2(S,1,1<<m){ 16 s=0; 17 F2(i,0,m) 18 if((S>>i)&1)s+=num[i]; 19 F2(i,0,m) 20 if((S>>i)&1)f[S]=Max(f[S],f[S^(1<<i)]+sum[s][i]-sum[s-num[i]][i]); 21 } 22 printf("%d",n-f[(1<<m)-1]); 23 return 0; 24 }
我这里是f[S]表示最多的不动元素,会稍微好算一点点……原理不变。
R1接下来的就不会了,都是大丧题。
【R2T1】
网址:点我
【题意简述】
给定n,对于x=1~n,求出 \(\sum_{i=1}^{n}x\;mod\;n\) 。
【思路】
就直接讲吧,我先打出了这样一个表格:
第 i 行第 j 列表示 i mod j 的值。
从左往右竖着看,第1列是0,第2列重复1,0循环,第3列重复1,2,0循环……
每加入一列,就给总结果加上了若干个等差数列。
对于等差数列的加法,可以用两次差分,最后两次前缀和的方法把其变为常数时间。
总共要进行 \(\sum_{i=1}^{n}\frac{n}{i}\;=\;n\;ln(n)\) 次差分。时间复杂度O(n*lnn)。
【代码】
1 #include<cstdio> 2 long long n,a[1000002]; 3 int main(){ 4 scanf("%d",&n); 5 for(int i=2;i<=n;++i){ 6 for(int j=0;j<=n;j+=i) 7 a[j]-=i,a[j+1]+=i; 8 ++a[0]; 9 } 10 for(int i=1;i<=n;++i) a[i]=a[i-1]+a[i]; 11 a[0]=0; 12 for(int i=1;i<=n;++i) a[i]=a[i-1]+a[i]; 13 for(int i=1;i<=n;++i) printf("%lld ",a[i]); 14 return 0; 15 }