前言
好爽!!第一次自己独立开出 1+2 的 A~E,虽然花了一个晚上()
一、A. A Wonderful Contest
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
if(a[i]==100)
{
Yes;
}
}
No;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
从最小得分 1 开始考虑,那么要想能达到 1 分,当且仅当存在一个问题有 100 个子问题,即一个子问题 1 分。只要存在,那么 1~100 分就都可以达成,之后又因为每个问题总分 100 分,所以 101~200 分也就可以达成,所以此时直接满足条件。
二、B. Artistic Balance Tree
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,m;
cin>>n>>m;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<int>b(m+1);
for(int i=1;i<=m;i++)
{
cin>>b[i];
}
vector<priority_queue<ll>>nums(2);
vector<int>ok(2);
for(int i=1;i<=n;i++)
{
int id=i%2;
nums[id].push(a[i]);
ok[id]|=(a[i]>0);
}
for(int i=1;i<=m;i++)
{
int id=b[i]%2;
if(!nums[id].empty()&&nums[id].top()>0)
{
nums[id].pop();
}
else
{
if(!ok[id])
{
nums[id].pop();
ok[id]=1;
}
}
}
ll ans=0;
for(int i=0;i<=1;i++)
{
while(!nums[i].empty())
{
ans+=nums[i].top();
nums[i].pop();
}
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这个题还是得想一下的。
注意到不管怎么执行区间翻转操作,元素下标的奇偶性是不会发生改变的。令翻转的长度为 len,因为每次相当于让一个位置 +2len 或 -2len,所以奇偶性肯定不变。那么也就是说,能换到当前位置的元素,其下标和这个位置的奇偶性必然相同。所以对于一个位置,总是可以将某个位置奇偶性和其相同的元素移动到这个位置。
那么之后就只需要每次贪心地删除所有和当前位置奇偶性相同的位置中,最大的正数元素即可,所以实现时直接开两个大根堆分别维护奇数位置和偶数位置的元素。注意若初始没有正数,此时就需要删除一个最大的负数,然后之后重复标记这个数即可。
三、C. Median Partition
byd 这个题真的抽象,属于是傻子克高手......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<typename T>
struct BIT{
vector<T>tree;
int n;
BIT(int _n,T v=0){
tree.resize(_n,v);
n=_n-1;
}
int lowbit(int i){
return i&-i;
}
void add(int i,T v){
while(i<=n){
tree[i]+=v;
i+=lowbit(i);
}
}
T sum(int i){
T ans=0;
while(i>0){
ans+=tree[i];
i-=lowbit(i);
}
return ans;
}
T query(int l,int r){
if(r>n||l>r){
return 0;
}
return sum(r)-sum(l-1);
}
//第k小
int kth(int k){
if(k<1||k>sum(n)){
return 0;
}
int p=0;
for(int i=1<<20;i;i>>=1){
if(p+i<=n&&tree[p+i]<k){
k-=tree[p+i];
p+=i;
}
}
return p+1;
}
};
void solve()
{
int n;
cin>>n;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<ll>sorted=a;
sort(sorted.begin()+1,sorted.end());
int len=1;
for(int i=2;i<=n;i++)
{
if(sorted[len]!=sorted[i])
{
sorted[++len]=sorted[i];
}
}
auto bs=[&](int v)->int
{
int l=1;
int r=len;
int m;
int ans;
while(l<=r)
{
m=l+r>>1;
if(sorted[m]>=v)
{
ans=m;
r=m-1;
}
else
{
l=m+1;
}
}
return ans;
};
for(int i=1;i<=n;i++)
{
a[i]=bs(a[i]);
}
vector<vector<int>>dp(n+1,vector<int>(len+1,-INF));
for(int v=1;v<=len;v++)
{
dp[0][v]=0;
}
for(int i=1;i<=n;i++)
{
BIT<int>tree(len+1);
for(int j=i;j>=1;j--)
{
tree.add(a[j],1);
int L=i-j+1;
if(L%2)
{
int cur=tree.kth((L+1)/2);
dp[i][cur]=max(dp[i][cur],dp[j-1][cur]+1);
}
}
}
int ans=0;
for(int v=1;v<=len;v++)
{
ans=max(ans,dp[n][v]);
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
根据之前的经验,对于中位数问题首先考虑二分答案,然后对于钦定的中位数 mid,按照大于等于和小于将原数字分别转化为 +1 和 -1,然后检查累加和即可。但思考后可以发现,这个方法只能判断中位数是否大于等于 mid,无法确定中位数是否是 mid。
考虑定义 为考虑前 i 个位置,能划分出的最大子数组个数。注意到 n 很小,所以自然能想到
枚举从前面哪个位置转移过来的解法。此时可以发现,可能存在整体数组一样,但每个子数组的中位数不同的情况,这样定义无法涵盖所有情况。
又因为中位数不考虑数字的数值,只关注大小关系,所以可以先对数组离散化。之后考虑定义 为考虑前 i 个位置,中位数为 j 的最大子数组个数。在暴力枚举转移时,考虑从后往前枚举最后一组范围,此时就需要动态维护数组中位数,这个可以考虑通过树状数组求第 k 大实现。
四、D. Permutation Construction
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<ll>pre(n);
for(int i=1;i<n;i++)
{
pre[i]=pre[i-1]+a[i];
}
vector<int>id(n);
for(int i=0;i<n;i++)
{
id[i]=i;
}
sort(id.begin(),id.end(),[&](int x,int y)
{
return pre[x]>pre[y];
});
vector<int>ans(n+1);
for(int i=0;i<n;i++)
{
ans[id[i]+1]=i+1;
}
for(int i=1;i<=n;i++)
{
cout<<ans[i]<<" ";
}
cout<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
pure guess......
对于逆序对价值的这个式子,发现是一个区间问题,考虑转化成前缀问题。那么在构建出前缀和数组 pre 后,此时对于位置数对 ,如果
,此时让这两个位置构建逆序对,价值必然是大于 0 的,所以考虑分配
。
所以可以想到,对下标位置根据前缀和从大到小排序,然后从 1 到 n 给每个位置填数即可。需要注意的是,由于价值区间左闭右开,所以考虑对排序下标左移一位,对 0 到 n-1 位置的前缀和进行排序,然后给 +1 位置填数即可。
五、E. Seek the Truth
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
int ask(char op,ll x)
{
cout<<op<<" "<<x<<endl;
int res;
cin>>res;
return res;
}
void solve()
{
int n;
cin>>n;
//a
cout<<0<<endl;
//query k
int res,k;
res=ask('I',0);
if(res==1)
{
k=1;
}
else
{
//S={0,c}
int pre=res;
res=ask('I',(1ll<<n)-1);
//c=11..11
if(res==pre)
{
res=ask('I',1);
if(res==pre)
{
k=2;
}
else
{
k=3;
}
cout<<"A "<<k<<" "<<(1ll<<n)-1<<endl;
return ;
}
res=ask('Q',(1ll<<n)-1);
if(res==1)
{
k=2;
}
else
{
k=3;
}
}
//query c
ll c=0;
if(k==1)
{
int pre=1;
for(int i=n-1;i>=0;i--)
{
res=ask('I',1ll<<i);
if(res>pre)
{
c|=(1ll<<i);
}
pre=res;
}
}
else if(k==2)
{
for(int i=n-1;i>=0;i--)
{
res=ask('Q',c|(1ll<<i));
if(res==2)
{
c|=(1ll<<i);
}
}
}
else if(k==3)
{
c=1ll<<(n-1);
for(int i=n-2;i>=0;i--)
{
res=ask('Q',c|(1ll<<i));
if(res==1)
{
c|=(1ll<<i);
}
}
//check highest bit
res=ask('I',c);
if(res!=3)
{
c^=(1ll<<n)-1;
}
}
cout<<"A "<<k<<" "<<c<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
无敌了这个题,把位运算考察了个遍!!
由 n+3 的次数和 k 的范围可以想到,先使用 3 次问出 k,再使用 n 次问出 c。那么首先根据 & 运算和 | 运算的性质,考虑首先选择 a=0 插入集合。此时,若集合大小还是 1,那么就说明是 & 运算,即 k=1。否则的话,考虑再把全 1 状态加入集合,此时 | 运算和 ^ 运算的集合分别就是 {c,11...11} 和 {c,~c},其中 ~c 表示按位取反。那么最后,再用第三次查询,问集合中大于等于 11...11 的数有几个,如果是 1 那么就说明是 | 运算,否则就说明是 ^ 运算。需要注意的是,若 c=11...11,这个时候需要进行特判。
之后,对三种情况分别考虑找出 n 次询问的方法。那么首先,对于 & 运算,那么直接每次问 1<<i&c 的结果即可。重点是后两种运算。
对于 | 运算,可以发现第一种操作很难判断每一位的状态,所以考虑第二种操作。考虑从高位到低位,对于之前构建出的 c 的高位,每次问集合中大于等于 c|(1<<i) 的个数。此时若个数是 2,那么就说明 c 的这一位是 1,否则就说明是 0。
最难的还是 ^ 运算,由 | 运算可以得到启发每次问大于等于某个数的个数。但可以发现不管 c 是多少,集合中最高位既有 0 状态也有 1 状态,所以无法确定 c 的具体值。
那么既然不知道最高位,不如假定最高位为 1,然后还是从次高位开始,每次问大于等于 c|(1<<i) 的个数。此时,若答案是 1 就说明最高位是 1 的那个数的当前位是 1。
那么在这样问完之后,此时的 c 就是最高位是 1 的那个数。那么就只需要再用最后剩下的一次,确定 c 是否需要取反即可。此时考虑将当前的这个 c 插入集合,若集合大小没变,那么就说明此时的 c 就是答案,因为异或的答案 0 已经存在。否则异或的答案就是 11...11,集合的大小会增大。
总结
这个 E 真的无敌!!