注意: 不要尝试在排行榜上找我(ducati),这场比赛本蒟蒻打的是虚拟赛。
Solution
贪心思路很明显,就是在一个非开头,非结尾的位置放一个 m m m,其他全放 0 0 0即可。这样答案就是 2 m 2m 2m。
注意特判 n = 1 n=1 n=1或 n = 2 n=2 n=2的情况:
①当 n = 1 n=1 n=1时,直接输出 0 0 0;
②当 n = 2 n=2 n=2时,直接输出 m m m,序列长这样: 0 0 0 m m m。
#include
#define int long long
using namespace std;
int t,n,k;
int a[1000005],dp[1000005],pre[1000005];
signed main()
{
cin>>t;
while (t--)
{
cin>>n>>k;
for (int i=1;i<=n;i++)
{
char ch;
cin>>ch;
a[i]=ch-'0';
}
for (int i=1;i<=n;i++) pre[i]=pre[i-1]+a[i];
int ans=1e9+7;
for (int i=1;i<=n;i++)
{
dp[i]=pre[i-1]-pre[0];
if (i>=k) dp[i]=min(dp[i],dp[i-k]+(pre[i-1]-pre[i-k]));
if (a[i]==0) dp[i]++;
ans=min(ans,dp[i]+(pre[n]-pre[i]));
}
cout<<min(ans,pre[n])<<endl;
}
return 0;
}
Solution
仍然是简单的贪心。
首先,考虑将 a i a_i ai换成 b j b_j bj的贡献,即为 b j − a i b_j-a_i bj−ai。那么,我们需要选择 k k k对 ( i , j ) (i,j) (i,j)( i i i两两不同, j j j两两不同),使得其贡献之和尽可能得大。
于是,我们将 a a a数组从小到大排序,将 b b b数组从大到小排序,然后从左到右依次用 b i b_i bi替换 a i a_i ai即可。注意当 b i < a i b_i
最后算一下在替换后 a a a数组中所有数的和即可。
上代码~
#include
#define int long long
using namespace std;
int t,n,k;
int a[200005],b[200005];
bool cmp(int x,int y)
{
return x>y;
}
signed main()
{
cin>>t;
while (t--)
{
cin>>n>>k;
for (int i=1;i<=n;i++) cin>>a[i];
for (int i=1;i<=n;i++) cin>>b[i];
sort(a+1,a+n+1);
sort(b+1,b+n+1,cmp);
int pos=0,ans=0;
for (int i=1;i<=n;i++)
{
if (pos==k) break;
if (a[i]<b[i]) a[i]=b[i],pos++;
else break;
}
for (int i=1;i<=n;i++) ans+=a[i];
cout<<ans<<endl;
}
return 0;
}
Solution
又是贪心题很无语 。
首先,很明显最终要把棋子全部移到最中间的那个格子,即坐标为 ( u p ( i / 2 ) , u p ( i / 2 ) ) (up(i/2),up(i/2)) (up(i/2),up(i/2))的格子。根据题意易得,把一个坐标为 ( a i , a j ) (a_i,a_j) (ai,aj)的棋子移动到 ( b i , b j ) (b_i,b_j) (bi,bj)的位置需要 m a x ( ∣ a i − b i ∣ , ∣ a j − b j ∣ ) max(|a_i-b_i|,|a_j-b_j|) max(∣ai−bi∣,∣aj−bj∣)次移动。
但是不能在 O ( n 2 ) O(n^2) O(n2)代价下算每个格子到中间格子的距离并求和。于是,为了想出正解,我们写下每个格子内的棋子移动到最中间格子的移动次数,规律就出来了。
比如,当 n = 5 n=5 n=5时如下图。其中方阵中第 i i i行第 j j j列的数表示 ( i , j ) (i,j) (i,j)到中间的格子的移动次数。
2 2 2 2 2
2 1 1 1 2
2 1 0 1 2
2 1 1 1 2
2 2 2 2 2
很明显答案就是上述方阵中各数的和。
观察一下方阵: 中间1个0,然后8个1,最后16个2。
容易发现,值为 i i i的数形成了一个空心方阵,其周长为 8 i 8i 8i,那么值为 i i i的所有数的和就是 8 i 2 8i^2 8i2。
找到 i i i的取值范围: 0 ≤ i ≤ u p ( n / 2 ) 0≤i≤up(n/2) 0≤i≤up(n/2),其中 u p ( n / 2 ) up(n/2) up(n/2)表示 n / 2 n/2 n/2向上取整。
综上所述,答案就是 Σ i = 0 u p ( n / 2 ) 8 i 2 Σ_{i=0}^{up(n/2)} 8i^2 Σi=0up(n/2)8i2。
如果想要 O ( 1 ) O(1) O(1)算,那么答案就是 4 k ( k + 1 ) ( 2 k + 1 ) 3 \frac{4k(k+1)(2k+1)}{3} 34k(k+1)(2k+1),其中 k = u p ( n / 2 ) k=up(n/2) k=up(n/2)。
Solution
我们每次需要找到一个长度最大的空区间并将它的中间位置给标上数。但是普通的查找需要的时间复杂度太大了。
于是,我们开始记录每个连续的区间,会更加方便快速地找长度最大的区间。
例如,当 n = 5 n=5 n=5时,每次操作前的区间序列如下:
① ( 1 , 5 ) (1,5) (1,5)
② ( 1 , 2 ) ( 4 , 5 ) (1,2)(4,5) (1,2)(4,5)
③ ( 1 , 0 ) ( 2 , 2 ) ( 4 , 5 ) (1,0)(2,2)(4,5) (1,0)(2,2)(4,5)
④ ( 1 , 0 ) ( 2 , 2 ) ( 4 , 3 ) ( 5 , 5 ) (1,0)(2,2)(4,3)(5,5) (1,0)(2,2)(4,3)(5,5)
⑤ ( 1 , 0 ) ( 2 , 1 ) ( 3 , 2 ) ( 4 , 3 ) ( 5 , 5 ) (1,0)(2,1)(3,2)(4,3)(5,5) (1,0)(2,1)(3,2)(4,3)(5,5)
⑥ ( 1 , 0 ) ( 2 , 1 ) ( 3 , 2 ) ( 4 , 3 ) ( 5 , 4 ) ( 6 , 5 ) (1,0)(2,1)(3,2)(4,3)(5,4)(6,5) (1,0)(2,1)(3,2)(4,3)(5,4)(6,5)
注意区间 [ l , r ] [l,r] [l,r]中,若 l > r l>r l>r则表示该区间为空。
所以说,我们每次找到长度最大的一个空区间 [ l , r ] [l,r] [l,r],找到它的 m i d mid mid并将它分成 [ l , m i d − 1 ] [l,mid-1] [l,mid−1]和 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]两个区间,再把它们摁回记录区间的容器。当然我们每次还要找最长的区间,注意区间 [ l , r ] [l,r] [l,r]的长度为 r − l + 1 r-l+1 r−l+1。
说了这么多,都是为下面的一句话做铺垫:
于是,我们需要一个排序规则与表示区间的结构体:
struct node
{
int l,r;
bool operator < (const node x) const//运算符重载
{
if (x.r-x.l+1!=r-l+1) return (x.r-x.l+1)>(r-l+1);
else return x.l<l;//选择最左边的最长区间
}
};
然后定义一下优先级队列:
std::priority_queue<node> q;
最后上AC代码~
#include
#define int long long
using namespace std;
int t,n;
int p[200005];
struct node
{
int l,r;
bool operator < (const node x) const
{
if (x.r-x.l+1!=r-l+1) return (x.r-x.l+1)>(r-l+1);
else return x.l<l;
}
};
std::priority_queue<node> q;
signed main()
{
cin>>t;
while (t--)
{
std::priority_queue<node> q;
cin>>n;
q.push((node){1,n});
for (int i=1;i<=n;i++)
{
int nowl=q.top().l,nowr=q.top().r;
q.pop();
int mid=(nowl+nowr)/2;
p[mid]=i;
q.push((node){nowl,mid-1});
q.push((node){mid+1,nowr});
}
for (int i=1;i<=n;i++) cout<<p[i]<<' ';
cout<<endl;
}
return 0;
}
Solution
不错的 d p dp dp题,绞尽脑汁终于想出来了。
状态设计 d p i dp_i dpi表示以 i i i为最后一个 1 1 1结尾的序列中,使得 1 1 1至 i i i形成好灯阵的最少需要改变的灯数量。
状态转移显然:
①如果它前面没有开着的灯了,那么它为1且它前面全都是0,需要改变的次数就是它前面 1 1 1的个数。
②如果它前面有开着的灯,那么它的状态就从 d p i − k dp_{i-k} dpi−k转移而来;易知区间 [ i − k + 1 , i − 1 ] [i-k+1,i-1] [i−k+1,i−1]内必须全是0,所以需要改变的次数就是 d p i − k dp_{i-k} dpi−k加上 [ i − k + 1 , i − 1 ] [i-k+1,i-1] [i−k+1,i−1]内1的个数。
当然,如果该数为0,现在要把它改成1;那么答案还需要加1。
用代码概括:
dp[i]=pre[i-1]-pre[0];
if (i>=k) dp[i]=min(dp[i],dp[i-k]+(pre[i-1]-pre[i-k]));
if (a[i]==0) dp[i]++;
注意计算不带修改的区间和时用前缀和最方便,即 Σ i = l r a i Σ_{i=l}^r a_i Σi=lrai= p r e r − p r e l − 1 pre_r-pre_{l-1} prer−prel−1。
最后,说一下可怕的卡点:
①看到 i f ( i > = k ) if (i>=k) if(i>=k)这一行了吗?如果不加会 R E RE RE,原因自行体会;
②有可能所有灯都是不开的,那这种灯阵也是好的!所以要特判!
cout<<min(ans,pre[n])<<endl;
事实上出题人特别良心,如果没加这一行,样例中的最后一个小测过不去;想当年一个卡点坑人,样例全过可就是WA一两个点,赛后看一看题解直想抽自己……
不说了,上代码~
#include
#define int long long
using namespace std;
int t,n,k;
int a[1000005],dp[1000005],pre[1000005];
signed main()
{
cin>>t;
while (t--)
{
cin>>n>>k;
for (int i=1;i<=n;i++)
{
char ch;
cin>>ch;
a[i]=ch-'0';
}
for (int i=1;i<=n;i++) pre[i]=pre[i-1]+a[i];
int ans=1e9+7;
for (int i=1;i<=n;i++)
{
dp[i]=pre[i-1]-pre[0];
if (i>=k) dp[i]=min(dp[i],dp[i-k]+(pre[i-1]-pre[i-k]));
if (a[i]==0) dp[i]++;
ans=min(ans,dp[i]+(pre[n]-pre[i]));
}
cout<<min(ans,pre[n])<<endl;
}
return 0;
}
太弱了,做不出来
评估一下难度吧。
A: 入门
B: 普及-
C: 普及-
D: 普及/提高-
E: 普及+/提高
撒花✿✿ヽ(°▽°)ノ✿撒花