前言
牛魔能不能别出 C 这种题了!!
一、A. Blocked
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];
}
sort(a.begin()+1,a.end(),greater<>());
for(int i=2;i<=n;i++)
{
if(a[i]==a[i-1])
{
cout<<-1<<endl;
return ;
}
}
for(int i=1;i<=n;i++)
{
cout<<a[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;
}
二、B. OIE Excursion
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<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
int j=i;
while(j<=n&&a[j]==a[i])
{
j++;
}
int len=j-i;
if(len>=m)
{
NO;
}
i=j-1;
}
YES;
}
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;
}
三、C. Grid L
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()
{
ll p,q;
cin>>p>>q;
ll k=2*p+4*q+1;
for(ll i=3;i*i<=k;i++)
{
if(k%i==0)
{
ll x=i;
ll y=k/i;
ll n=(x-1)/2;
ll m=(y-1)/2;
ll X=n*(m+1);
ll Y=m*(n+1);
if(q<=min(X,Y))
{
cout<<n<<" "<<m<<endl;
return ;
}
}
}
cout<<-1<<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;
}
对于这种构造题,还是考虑先找到必要条件,然后证明充分性。
首先,对于一个 的网格,其必然有
条横向边,有
条纵向边。又因为一条线和一个拐必然分别提供 1 条和 2 条边,那么其就必须满足
。之后发动注意力,可以发现对于一个拐,不管怎么旋转,其必然包含一条横向边和一条纵向边,那么就存在约束
。
之后,考虑找出一种构造策略,来证明只要 ,必然可以放下这 q 个拐。令宽小于等于高,即
,此时就需要证明网格中可以放下
个拐。也就是说,对于
个横边,都能独立地与一条相邻竖线配对。
首先对于最左一列,让每条横线和左侧往上的竖线匹配。对于最顶上剩下的横线,让其匹配右侧往下的竖线。那么当来到第二列时,下面的横线都可以照常和左侧往上的竖线匹配。但对于最上方的两条横线,此时由于左侧的横线被占用,那么就只能也和右侧往下的竖线匹配。之后以此类推,可以发现当每条横线都只能与右侧往下的竖线匹配时,此时构成了一个完整的 的网格,符合上述约束,证明结束。
发现 是两数相加的形式,不是很好解决,那么考虑转化成两数相乘的形式。首先考虑拆开括号得
,然后等式两边同时乘以 2 再 +1 得
。此时可以发现右侧可以进行因式分解,有
。
由于要求 ,那么右侧就是两个大于等于 3 的奇数相乘。此时就可以枚举
所有大于等于 3 的奇数因子,每次代入第二个约束判断,合法就直接输出即可。时间复杂度为
,计算量大概是
。
四、D1. Unique Values (Easy version)
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(vector<int>a)
{
cout<<"? "<<a.size();
for(auto x:a)
{
cout<<" "<<x;
}
cout<<endl;
int res;
cin>>res;
return res;
}
void solve()
{
int n;
cin>>n;
auto check=[&](int p)->int
{
vector<int>a;
for(int i=1;i<=p;i++)
{
a.push_back(i);
}
int left=ask(a);
vector<int>b;
for(int i=p+1;i<=2*n+1;i++)
{
b.push_back(i);
}
int right=ask(b);
if(left<right)
{
return 2;
}
if(left>right)
{
return 1;
}
if((p-left)%2)
{
return 3;
}
return 0;
};
vector<int>pos;
for(int t=1;t<=3;t++)
{
int l=1;
int r=2*n+1;
int m;
int ans;
while(l<=r)
{
m=l+r>>1;
if(check(m)>=t)
{
ans=m;
r=m-1;
}
else
{
l=m+1;
}
}
pos.push_back(ans);
}
cout<<"! ";
for(auto x:pos)
{
cout<<x<<" ";
}
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;
}
根据这个次数,不难想到需要二分。
对于当前查询集合 S,令 T 为其补集,那么若这个特殊数字在 S 中出现了 x 次,那么在 T 中就出现了 3-x 次。而对于普通数字,若两次全出现在同一个集合中,那么是完全不会产生贡献的。而若在两个集合中各出现一次,此时对两集合共同贡献一次,所以可以考虑关注两集合询问结果的差异。
所以,对于询问出的 S 的结果 x 和 T 的结果 y,那么由上述分析可得,普通数字对两者的大小关系其实是没有贡献的。那么若 x>y,那么就说明特殊数字在 S 中出现了 1 次。而若 x<y,那么就说明特殊数字在 S 中出现了 2 次。
而当 x=y 时,考虑让 S 的大小减去 x,那么剩下的就是出现两次及以上的数的个数。此时,若这个结果是奇数,就说明特殊数字在 S 中出现了 3 次,否则就说明特殊数字在 S 中出现了 0 次。
有了这个结论,就可以每次问前缀和后缀的结果,然后判断是去左侧还是去右侧。那么就可以进行三次二分,每次找到当前轮中特殊数字出现的最左位置,次数为 。
五、D2. Unique Values (Hard version)
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(vector<int>a)
{
cout<<"? "<<a.size();
for(auto x:a)
{
cout<<" "<<x;
}
cout<<endl;
int res;
cin>>res;
return res;
}
void solve()
{
int n;
cin>>n;
vector<int>to(2*n+2);
auto check=[&](int p)->int
{
vector<int>a;
for(int i=1;i<=p;i++)
{
a.push_back(to[i]);
}
int left=ask(a);
return (p-left)%2;
};
vector<int>pos;
int pre=1;
for(int t=1;t<=3;t++)
{
int id=1;
for(int i=pre;i<=2*n+1;i++)
{
to[id++]=i;
}
for(int i=1;i<pre;i++)
{
to[id++]=i;
}
int l=1;
int r=2*n+1;
int m;
int ans;
while(l<=r)
{
m=l+r>>1;
if(check(m))
{
ans=m;
r=m-1;
}
else
{
l=m+1;
}
}
pos.push_back(to[ans]);
pre=to[ans];
}
reverse(pos.begin(),pos.end());
cout<<"! ";
for(auto x:pos)
{
cout<<x<<" ";
}
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;
}
对于 33 次的要求,考虑将每次问两次的部分省去。注意到如果不问后缀,那么就只能通过让 S 的大小减去 x 的结果进行判断。此时,如果是奇数,那么就能直接确定特殊数的个数是 3,其他什么也判断不了。
也就是说,此时就只能二分找出第三个特殊数字出现的位置。那么在找出第三个位置后,考虑让数组转一下,让第三个位置来到开头,然后再去找第三个出现的位置。此时二分出的答案,就是第二个特殊数字出现的位置了。
总结
这个 D2 转一下的操作也太妙了......