鸽了好久……也颓了很多。
这场的题目A、B、C1、D是不难的。
A逆序数,B位运算计数、C1贪心orDP、C2线段树or贪心、D排序组合数学+树状数组or直接扫一遍、E斜率优化DP。
这场用Pokémon和Ori做题面,双厨狂喜(嘿我现在就在听Fleeing Kuro (alternate))。
A.Cubes Sorting
思路
冒泡排序最坏情况为严格单调递减,除了这种情况一定比 n × ( n − 1 ) 2 \frac{n\times(n-1)}{2} 2n×(n−1)小。
代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
signed main(signed argc, char const *argv[])
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#ifdef DEBUG
freopen("input.in", "r", stdin);
// freopen("output.out", "w", stdout);
#endif
int t,n,pre,nex;
cin>>t;
while(t--)
{
cin>>n>>pre;
bool ok=0;
for(int i=2;i<=n;i++)
{
cin>>nex;
if(nex>=pre)
ok=1;
pre=nex;
}
cout<<(ok?"YES":"NO")<<endl;
}
return 0;
}
B.Rock and Lever
思路
开个桶计录每个位作为最高位的数量,然后遍历32个桶, C ( x , 2 ) C(x,2) C(x,2)即可。
代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
#define int ll
#define ff first
#define ss second
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
signed main(signed argc, char const *argv[])
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#ifdef DEBUG
freopen("input.in", "r", stdin);
// freopen("output.out", "w", stdout);
#endif
int t,n,tmp;
cin>>t;
while(t--)
{
cin>>n;//计算a&b > a^b的个数
vector<int>mp(33,0);
for(int i=1;i<=n;i++)
{
cin>>tmp;
for(int k=32;k>=0;k--)
{
if(tmp&(1ll<<k))
{
mp[k]++;
break;
}
}
}
ll ans=0;
for(int i=0;i<=32;i++)
ans+=mp[i]*(mp[i]-1)/2;
cout<<ans<<endl;
}
return 0;
}
C1 & C2.Pokémon Army (easy & hard version)
题意
给你 n n n个正整数 a 1 … a i a_1 \dots a_i a1…ai,你从中选出一个序列,使得序列中奇数项元素减去偶数项元素的和最大。
C2多出了 q q q次交换,每次给定 l l l与 r r r,交换 a a a中的 a l a_l al和 a r a_r ar。
输出 q + 1 q+1 q+1行答案。
思路
先不考虑修改,可以发现为了使得答案最大,每次一定选取奇数个。
我们要让每个奇数项尽量大、每个偶数项尽量小。
所以第一个数一定会挑选第一个出现的峰,而之后都是两个两个加入进来,因为偶数项做负贡献,其后必定加入一个大于它的数来增加贡献。这个奇数项一定为一个谷,而偶数项一定为峰。
可以发现一个规律,即所有的峰和谷都必定会计入答案,其中峰对答案有正贡献,谷对答案有负贡献。
C1就可以A掉了。
因此,在C2中,考虑如何维护好每个峰和谷的贡献。
可以发现,每个数是不是峰或者谷只与它相邻数以及它本身的大小关系有关。因此,修改下标为
l
l
l的数的时候,受到影响的峰和谷只有
l
−
1
,
l
,
l
+
1
l-1,l,l+1
l−1,l,l+1这三个数。
所以交换操作可以看作首先消除
[
l
−
1
,
l
+
1
]
[l-1,l+1]
[l−1,l+1]和
[
r
−
1
,
r
+
1
]
[r-1,r+1]
[r−1,r+1]区间的峰和谷的贡献,再计入数值更改后该区间峰和谷的贡献.
当
l
l
l和
r
r
r区间有交集时,讨论会变得非常复杂,可以使用set去重来避免讨论。
C1代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
//#define int ll
#define ff first
#define ss second
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
//#define DEBUG//#define lowbit(x) ((x) & -(x))//<<setiosflags(ios::fixed)<<setprecision(9)
signed main(signed argc, char const *argv[])
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie
(0);
#ifdef DEBUG
freopen("input.in", "r", stdin);
// freopen("output.out", "w", stdout);
#endif
int t,n,q;
cin>>t;
while(t--)
{
cin>>n>>q;//q次交换l与r
ll ans=0,pre=0,low=0,tmp;
for(int i=1;i<=n;i++)
{
cin>>tmp;
if(tmp>pre)
{
ans+=tmp-low;
low=tmp;
}
else
low=min(low,tmp);
pre=tmp;
}
cout<<ans<<endl;
}
return 0;
}
C2代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
//#define int ll
#define ff first
#define ss second
const int maxn=3e5+10,inf=0x3f3f3f3f,mod=1000000007;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
int a[maxn];
signed main(signed argc, char const *argv[])
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#ifdef DEBUG
freopen("input.in", "r", stdin);
// freopen("output.out", "w", stdout);
#endif
int t,n,q,l,r;
cin>>t;
while(t--)
{
cin>>n>>q;
for(int i=1;i<=n;i++)
cin>>a[i];
a[0]=a[n+1]=-inf;
ll ans=0;
for(int i=1;i<=n;i++)
{
if(a[i-1]<a[i]&&a[i]>a[i+1])
ans+=a[i];
if(a[i-1]>a[i]&&a[i]<a[i+1])
ans-=a[i];
}
cout<<ans<<endl;
auto del=[&](int pos){
if(pos<1||pos>n)
return;
if(a[pos-1]<a[pos]&&a[pos]>a[pos+1])
ans-=a[pos];
if(a[pos-1]>a[pos]&&a[pos]<a[pos+1])
ans+=a[pos];
}
auto add=[&](int pos){
if(pos<1||pos>n)
return;
if(a[pos-1]<a[pos]&&a[pos]>a[pos+1])
ans+=a[pos];
if(a[pos-1]>a[pos]&&a[pos]<a[pos+1])
ans-=a[pos];
};
while(q--)
{
cin>>l>>r;
set<int>now{l-1,l,l+1,r-1,r,r+1};
for(auto &x:now)
del(x);
swap(a[l],a[r]);
for(auto &x:now)
add(x);
cout<<ans<<endl;
}
}
return 0;
}
D.Rescue Nibel!
题意
有 n n n盏灯,每盏灯都有一个点亮时间 [ l i , r i ] [l_i,r_i] [li,ri],你要从中选出 k k k盏,使得这 k k k盏存在一个瞬间全部灯都是点亮的,输出符合条件的组合数。
思路
这题比C2简单,很快就口胡出来了……
代码也很清晰,将所有开灯关灯时间点做好标记按时间点排序。
开一个变量维护此时已经亮着的灯的数目,按时间遍历。
每当一个灯点亮的时候,假如此时数目 ≥ k \ge k ≥k,则计入答案,为了避免重复,新加入的这一个是一定选入的,剩下的 k − 1 k-1 k−1盏在之前维护的值里选,即 C ( 之 前 点 亮 的 数 目 , k − 1 ) C(之前点亮的数目,k-1) C(之前点亮的数目,k−1)。
代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
//#define int ll
#define ff first
#define ss second
const int maxn=1e6+10,inf=0x3f3f3f3f,mod=998244353;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
ll fac[maxn],a[maxn];
ll quick(ll a,ll b){//快速幂
ll ans=1;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b/=2;
}
return ans%mod;
}
ll ccc(ll n,ll m){//求组合数
return (fac[n] * quick(fac[m], mod - 2) % mod * quick(fac[n - m], mod - 2) % mod)%mod;
}
void initccc()
{
fac[0] = 1;
for (int i = 1; i <maxn ;i++){
fac[i] = fac[i - 1] * i % mod;
}
}
signed main(signed argc, char const *argv[])
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,k,l,r;
initccc();
vector<pii>vec;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>l>>r;
vec.push_back(pii(l,1));
vec.push_back(pii(r+1,-1));
}
sort(vec.begin(),vec.end());
ll ans=0,now=0;
for(pii &x:vec)
{
now+=x.ss;
if(x.ss==1)
ans=(ans+ccc(now-1,k-1))%mod;
}
cout<<ans<<endl;
return 0;
}
/*
*选k个灯,存在同时亮
*求种类总数
*/
E.Battle Lemmings
题意
有 n n n个守卫排成一行,编号为 1 1 1到 n n n。这些守卫里有的拿着盾牌,有的没有,一个守卫只能同时拿一个盾牌。
称一对守卫是被保护的,当且仅当这两个守卫都没拿盾牌,但他们之间有守卫拿了盾牌。
每一秒,指挥官可以下达两种命令中的一种,分别是:
- 选一个带盾牌的守卫,把它的盾牌给他左边的守卫。
- 选一个带盾牌的守卫,把它的盾牌给他右边的守卫。
请求出时刻 0 0 0 到 n × ( n − 1 ) 2 \frac{n \times (n - 1)}{2} 2n×(n−1)中每个时刻最多有多少对守卫是被保护的。
思路
一对不带盾的守卫只有在他们中间存在持盾守卫才有贡献,因此正难则反。总贡献可以通过所有无盾守卫的总对数减去无贡献对数。
设无盾守卫数目为 c 0 c_0 c0,持盾守卫数目为 c 1 c_1 c1,一共有 k k k段连续的 0 0 0,第 i i i段数目为 l i l_i li,则 a n s = c 0 × ( c 0 − 1 ) 2 − ∑ i = 1 k l i × ( l i − 1 ) 2 ans=\frac{c_0 \times (c_0 -1)}{2}-\sum\limits^{k}_{i=1}{\frac{l_i\times(l_i -1)}{2}} ans=2c0×(c0−1)−i=1∑k2li×(li−1)。
对于两个状态间的最小转化步数,从左扫到右,累加两个状态按次序的每个 1 1 1的下标差的绝对值。
令 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]的含义为:计算了前 i i i位,第 i i i位为 1 1 1,(对于前 k k k个 1 1 1)已经进行了 j j j次操作,前 i i i位有 k k k个 1 1 1的 [ 1 , i ] [1,i] [1,i]区间的最小不受保护对数。转移的时候枚举下一个 1 1 1的位置来进行更新。
比较令人困惑的地方在于,第一个 1 1 1的操作次数为什么是 d p [ i ] [ a b s ( p 1 − i ) ] [ 1 ] = ( i − 1 ) ∗ ( i − 2 ) / 2 dp[i][abs(p_1-i)][1]=(i-1)*(i-2)/2 dp[i][abs(p1−i)][1]=(i−1)∗(i−2)/2,可能原本在 i i i的左面有不止一个 1 1 1,移动次数也不止 a b s ( p 1 − i ) abs(p_1-i) abs(p1−i)。
因为此时这个 d p dp dp状态只考虑了第一个 1 1 1对于操作次数的贡献,之后再进行转移的时候才会逐步累计其后 1 1 1对操作次数的贡献。
普通转移都整不明白,斜率优化以后再说吧。
代码
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//#define int ll
const int maxn=85,inf=0x3f3f3f3f,mod=1000000007;
int a[maxn],pos[maxn];
int dp[maxn][maxn*maxn][maxn];
signed main(signed argc, char const *argv[])
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,c0=0,c1=0;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(a[i])
pos[++c1]=i;
else
c0++;
}
memset(dp,inf,sizeof(dp));
//dp[i][j][k]表示第i位为1,使用j次操作,前i个含有k个1
//状态下[1,i]的最小未受保护对数
int lim=n*(n-1)/2;
for(int i=1;i<=n;i++)
{//将第一个1移到i处
dp[i][abs(pos[1]-i)][1]=(i-1)*(i-2)/2;//[1,i-1]全部为0
for(int j=0;j<=lim;j++)
{//枚举操作步数j
for(int k=1;k<=i&&k<=c1;k++)
{//枚举[1,i]中1的数量k
if(dp[i][j][k]==inf)
continue;
int rest=c1-k;//剩下的1数目
for(int l=i+1;l<=n-rest+1;l++)//枚举下一个1来进行转移
{//将第k+1个1移到l处
int nex=j+abs(pos[k+1]-l);//计算步数
if(nex<=lim)//
dp[l][nex][k+1]=min(dp[l][nex][k+1],dp[i][j][k]+(l-i-1)*(l-i-2)/2);
}
}
}
}
int res=c0*(c0-1)/2;
for(int j=0;j<=lim;j++)
{
for(int i=c1;i<=n;i++)//第i位放最后一个1,后面补充剩下的0的贡献
res=min(res,dp[i][j][c1]+(n-i)*(n-i-1)/2);
cout<<c0*(c0-1)/2-res<<' ';
}
return 0;
}