【洛谷】【洛谷月赛】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 }

 

posted @ 2017-04-18 21:29  粉兔  阅读(567)  评论(0编辑  收藏  举报