Codeforces Round #744 (Div. 3)
A
AC代码
#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e5 ,mod=1e9 + 7;
void solve()
{
int a,b,c;
a = b = c = 0;
string str;
cin>>str;
for(int i=0 ;i<str.size();i++){
if(str[i]=='A')a++;
else if(str[i]=='B')b++;
else c++;
}
if(a + c == b) cout << "YES\n";
else cout << "NO\n";
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
int T;cin>>T;
while(T--)
solve();
return 0;
}
B
题目
给一个序列 , 不断用一个偏移操作,最终使之有序,模拟这个过程,操作最大次数限制为序列长度。
操作:每次选取一段区间,向左偏移一个偏移量d。
思路
- 我们每次把一个数字放到它该在的地方,如此重复n次一定可以得到答案,且总数不会超过n。
- 因此我们只要每次找到第一小的放到第一个,第二小的放到第二个,第三小的放到第三个···以此类推。
- 需要说明的是,每次放好后就不用操作,又因为排好的段连续,所以后续的操作不会影响之前排好的。
AC代码
#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e5 ,mod=1e9 + 7;
int n;
int b[N];
int a[N];
void get_mn(int st,int &id,int &mn)
{
for(int i = st;i<n;i++){
if(mn > a[i]) mn = a[i], id = i;
}
}
struct Ans{
int l,r,d;
}ans[N];
void solve()
{
cin >> n;
for(int i =0 ;i< n ;i++) cin>> a[i];
int id = 0, mn = inf;
int cnt = 0;
for(int i = 0;i < n ;i++){
mn = inf;
get_mn(i,id,mn);
int l = i , r = id ;
int d = r - l;
if(!d)continue;
int tmp = a[id];
for(int j = r-1 ; j>= l; j--){
a[j+1] = a[j];
}
a[i] = tmp;
ans[cnt++] = {l+1,r+1,d};
}
cout << cnt << endl;
for(int i = 0;i< cnt ;i++){
cout <<ans[i].l << ' '<<ans[i].r<<" "<<ans[i].d<<endl;
}
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
int T;cin>>T;
while(T--)
solve();
return 0;
}
C
题目
给一个地图,看判断是否可以由最小长度为k的“tick”构成,
tick:一个左右对称的对勾,它的长度为他的臂长
思路
- 对每一个被填充部分,将其作为对勾的中心,向左上右上搜索,进行可行性判断即可,代码实现有一些细节。
AC代码
#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e2 ,mod=1e9 + 7;
int n , m,k;
char mm[N][N];
bool vis[N][N];
int d[2][2] ={{1,1},{-1,1}};
int dfs_l(int x,int y,int op)
{
int nx = x - 1,ny = y - 1;
int res = 0;
if(op>0)vis[nx][ny] = 1;
if(nx<0||ny<0||nx>=n||ny>=m||mm[nx][ny]!='*') return 0;
return dfs_l(nx,ny,op-1) + 1;
}
int dfs_r(int x,int y,int op)
{
int nx = x - 1,ny = y +1;
int res = 0;
if(op>0)vis[nx][ny] = 1;
if(nx<0||ny<0||nx>=n||ny>=m||mm[nx][ny]!='*') return 0;
return dfs_r(nx,ny,op-1) + 1;
}
void solve()
{
memset(vis,0,sizeof vis);
cin >> n>>m >>k;
for(int i = 0;i<n ;i++)
for(int j = 0; j<m; j++)
cin>> mm[i][j];
int cnt = 0;
for(int i =0;i<n;i++)
for(int j = 0;j<m;j++){
if(mm[i][j] == '*'){
cnt ++;
int l = dfs_l(i,j,0), r = dfs_r(i,j,0);
// cout << l<<' '<<r<<endl;
if(min(l,r) >= k){
vis[i][j] = 1;
int mn = min(l,r);
dfs_l(i,j,mn),dfs_r(i,j,mn);
}
}
}
for(int i =0 ;i<n ;i++)
for(int j =0;j<m;j++)
cnt -=vis[i][j];
if(!cnt) cout<<"YES\n";
else cout<<"NO\n";
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
int T;cin>>T;
while(T--)
solve();
return 0;
}
/*
1
5 5 1
.....
*...*
.*.*.
..*.*
...*.
*/
D
题目
一些人谈话,每个人有一个谈话次数限制,每谈一次话,俩人都减少谈话次数,问最大谈话次数是什么
思路
- 我们贪心的考虑这个问题,为了尽可能不浪费,也就是不让人落单,直觉上认为谈话次数最多的人,最有可能被剩下
- 因此我们每次让次数最多的人参与谈话即可,这可以用优先队列很简单的实现。
AC代码
#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 2e5 ,mod=1e9 + 7;
int n;
struct Ans{
int x,y;
} ans[N];
priority_queue<PII>q;
void solve()
{
ll tot = 0;
while(q.size())q.pop();
scanf("%d",&n);
for(int i = 1;i<=n;i++){
int t;
scanf("%d",&t);
if(t) q.push({t,i});
}
int len = 0;
while(q.size()>1){
PII u = q.top();
q.pop();
PII v = q.top();
q.pop();
tot ++;
ans[len++] = {u.second,v.second};
u.first --;
v.first --;
if(u.first > 0){
q.push(u);
}
if(v.first > 0){
q.push(v);
}
}
printf("%lld\n",tot);
for(int i = 0;i<len;i++){
printf("%d %d\n",ans[i].x,ans[i].y);
}
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
int T;cin>>T;
while(T--)
solve();
return 0;
}
E1
题目
用deque操作一个序列,求字典序最小的那个序列。
思路
- 贪心,只需要比较队首元素和要加入的元素的大小即可,直接拿deque模拟。
AC代码
#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 2e5 ,mod=1e9 + 7;
int n;
int a[N];
deque<int>q;
void solve()
{
while(q.size())q.pop_back();
scanf("%d",&n);
for(int i = 0;i<n;i++)scanf("%d",a+i);
for(int i = 0;i<n;i++){
if(q.empty())q.push_back(a[i]);
else{
int t =q.front();
if(a[i]<=t)q.push_front(a[i]);
else q.push_back(a[i]);
}
}
for(auto it:q){
printf("%d ",it);
}
puts("");
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
int T;cin>>T;
while(T--)
solve();
return 0;
}
E2
题目
给一个序列,用deque操作这个序列,求所有可得到的序列中,逆序数的最小值。
思路
- 考虑一次操作,插入一个元素 a i a_i ai,只有插入队首或队尾,插入队尾时, [ a 1 , a i − 1 ] [a_1,a_{i-1}] [a1,ai−1]中比 a i a_i ai大的元素提供答案的增量,插入队首时, [ a 1 , a i − 1 ] [a_1,a_{i-1}] [a1,ai−1]中小于和等于 a i a_i ai的元素个数提供答案的增量。
- 对于每一次操作,可以发现和元素的顺序无关,因此前状态的选择不会对后过程产生任何影响,因此每次的最优局部解就可以得到全局最优解,所以每次贪心最小答案增量即可。
- 可以发现答案的增量要么是 [ a 1 , a i − 1 ] [a_1,a_{i-1}] [a1,ai−1]和 a i a_i ai的逆序数,要么是正序数(即比 a i a_i ai大的元素个数或者小于等于 a i a_i ai的元素个数)
- 那么应该用什么方法实现快速的求出这个序列一段前缀的逆序数呢?树状数组可以做到。
树状数组求逆序
关于这个问题,首先明确什么是树状数组,网上有很多优秀博客可以参考,这里不过多赘述。
然后对于用它来求逆序数,其思维过程如下:
- 我们首先朴素的求逆序,可以采用维护一个 c n t cnt cnt数组,记录某数字出现的次数。
- 显然当我们遍历到序列上的某个数之后,我们向前搜索所有不大于它的数字的个数,再减去总数就是逆序数了,这显然可以看成不断对 c n t cnt cnt数组求前缀和单点更新的操作,这就可以用树状数组来维护了。
- 所以每次遍历到一个 a i a_i ai,只要通过 l o w b i t lowbit lowbit求出前缀和,就可以得到它的正序数,进而得到逆序数,之后把这个数插入树状数组,它所提供的增量为1(即 a i a_i ai的出现次数增加一)
更多细节见注释
AC代码
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
#define lowbit(x) x&-x
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 2e5 ,mod=1e9 + 7;
int n;
int a[N],b[N],tr[N];
void add(int p ,int v)// 更新操作
{
for(int i = p;i<= n ;i+= lowbit(i)) tr[i] +=v;
}
int getsum(int p ) // 得到前缀和
{
int res = 0;
for(int i = p;i>0;i-= lowbit(i)) res += tr[i];
return res;
}
void solve()
{
memset(tr,0,sizeof tr);
scanf("%d",&n);
for(int i =1 ;i<= n ;i++) scanf("%d",a+i), b[i] = a[i];
sort(b+1,b+1+n);// 因为只关心相对大小,进行一个离散化
for(int i = 1;i<= n ;i++) a[i] = lower_bound(b+1,b+1+n,a[i]) - b;
ll res = 0;
for(int i = 1;i<= n ;i++){
res += min(getsum(a[i]-1),i-1 - getsum(a[i]));// 正序,所有比它小的 逆序,总数 - 所有小于等于它的
add(a[i],1);// update
}
printf("%lld\n",res);
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
int T;cin>>T;
while(T--)
solve();
return 0;
}
F
题目
对一个01序列进行一个操作,试着把它化为全是0的序列,求最小操作次数
操作:给一个步长d,每次把序列向右移动d,再和之前的序列按位取与。
思路
- 首先注意到如果对单独一位的元素进行分析,如果对它进行无限次偏移,它总是会固定出现在某些特定位置上
如:对于 a 0 a_0 a0,它会出现在 d , 2 d , 3 d . . . d,2d,3d... d,2d,3d...直到重新回到 0 0 0这一位置,就像一条衔尾蛇,不论从哪里起始,都会绕回开始的位置,所以这个操作具有周期性的同时,也说明了对某以一特定的起点,必然和某些点无关,所以我们可以把这个序列划分为很多个串,每个串之间是彼此独立的。每个串上的元素不会对其他串上元素有所影响。 - 又考虑到&的性质,只要这一个串中有一个0,那么整个串必然能在有限次操作中被更新为0。
- 思路已经很显然了,只要找到一个串的起点,从这个串的第一个0开始遍历,每次遇到1,就更新一次步数,最后这些串的最大值就是我们的答案了
AC代码
时间复杂度: O ( n ) O(n) O(n)
#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
#define lowbit(x) x&-x
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e6 ,mod=1e9 + 7;
int n;
int a[N];
bool vis[N];
int stp[N];
int d;
int find0(int id)// 找第一个0
{
int st_id = id;
if(a[id]==0) return id;
(id+=d)%=n;
while(id!=st_id){
if(!a[id]) return id;
(id+=d)%=n;// 模拟偏移
}
return n+1;
}
void solve()
{
memset(vis,0,sizeof vis);
memset(stp,0,sizeof stp);
cin>>n>>d;
for(int i =0;i<n;i++)cin>>a[i];
for(int i=0;i<d;i++){
for(int j = find0(i);!vis[j];(j+=d)%=n){
if(j == n+1){// 无0特判
cout<<-1<<endl;
return;
}
vis[j] = 1;
if(a[j]) stp[j] = stp[(j-d+n)%n] + 1;// 遇到1更新步数,它一定由上一个转移过来,
}
}
cout<< *max_element(stp,stp+n) << endl;
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
int T;cin>>T;
while(T--)
solve();
return 0;
}