Codeforces Round #602 (Div. 2, based on Technocup 2020 Elimination Round 3)
题意:有t组数据,每组数据给出n个范围[Li,Ri],求与n个范围都有交集的最小范围[Ans_L,Ans_R]的长度([L,R]的长度定义为R-L)
思路:这个区间只要从所有区间右端点的最小值覆盖到所有区间左端点的最大值即可。

#include<bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int total;
cin>>total;
while(total--){
int n,x,y;
cin>>n;
if(n==1) {
cin>>x>>y;
cout<<0<<endl;
continue;
}else{
int qian=0,hou=1e9;
for(int i=1;i<=n;i++){
cin>>x>>y;
qian=max(qian,x);
hou=min(hou,y);
}
int ans=max(0,qian-hou);
cout<<ans<<endl;
}
}
return 0;
}
题意:t组数据,每组数据给你n个信息,每个信息qi告诉你当前序列的前i个数中最大的是qi。问能否构造数列。
思路:从第一位依次构造,如果当前位置给出的最大值没出现过,那么该最大值在此位置第一次出现,所以这个位置构造为给出的最大值,若之前已经出现过,则从小于该最大值的数中选择任意一个没用过的数构造为该位置出现的数。若没有数可以选择,则该序列无法构造。

#include<bits/stdc++.h>
using namespace std;
int shu[100005];
int ans[100005];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int total;
cin>>total;
while(total--){
int n,get_num;
bool flag=true;
cin>>n;
unordered_set<int>tree;
set<int>num;
for(int i=1;i<=n;i++){
num.insert(i);
}
for(int i=1;i<=n;i++){
cin>>shu[i];
}
for(int i=1;i<=n;i++){
if(tree.find(shu[i])==tree.end()){
tree.insert(shu[i]);
num.erase(shu[i]);
ans[i]=shu[i];
}else{
for(auto j=num.begin();j!=num.end();j++){
if((*j)<shu[i]){
ans[i]=(*j);
num.erase(j);
break;
}else if(*j>shu[i]){
flag=false;
break;
}
}
if(!flag) break;
}
}
if(!flag) cout<<-1<<endl;
else{
for(int i=1;i<=n;i++){
cout<<ans[i]<<" ";
}cout<<endl;
}
}
return 0;
}
题意:有一个房间,有许多的括号,长度为n,如:“(())())(()”,现在让你将括号整理干净,干净的标准是该括号序列合法,且它的所有前缀序列中恰好有k个是合法的序列。你的每次操作是将区间为[L,R]的括号序列进行反转(即交换L和R位置的括号,交换L+1和R-1位置的括号……直到区间中间),输出操作次数和每次的操作区间(在n次内完成)。
思路:题目保证有解,所以顺序构造即可。可以先构造k-1个单对的括号“()”,最后剩余的n-2×(k-1)个括号构成大的嵌套括号“(((())))”,如n=8,k=2的“()(())()”可以构造为“()((()))” 。循环从第一位到最后一位,如果当前位置i的括号符合构造后的括号,则不需任何操作,若不符合构造后的括号,则往后循环找到第一个位置j,j位置的括号为i位置需要的括号,并对[i,j]进行反转操作。所有的[i,j]即为答案。

#include<bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int total;
cin>>total;
while(total--){
int n,k,cont_l=0,cont_r=0;
vector<pair<int,int> >ans;
string s;
cin>>n>>k;
cin>>s;
for(int i=0;i<s.size();i++){
if((i+2)/2<k){
if(i%2==0){
if(s[i]!='('){
for(int j=i+1;j<s.size();j++){
if(s[j]=='('){
ans.push_back(make_pair(i,j));
int l=i,r=j;
while(l<r){
swap(s[l],s[r]);
l++;r--;
}
break;
}
}
}
}else{
if(s[i]!=')'){
for(int j=i+1;j<s.size();j++){
if(s[j]==')'){
ans.push_back(make_pair(i,j));
int l=i,r=j;
while(l<r){
swap(s[l],s[r]);
l++;r--;
}
break;
}
}
}
}
}else{
int cont=(n-(k-1)*2);
if(cont_l<cont){
if(s[i]!='('){
for(int j=i+1;j<s.size();j++){
if(s[j]=='('){
ans.push_back(make_pair(i,j));
int l=i,r=j;
while(l<r){
swap(s[l],s[r]);
l++;r--;
}
break;
}
}
}
}else{
if(s[i]!=')'){
for(int j=i+1;j<s.size();j++){
if(s[j]==')'){
ans.push_back(make_pair(i,j));
int l=i,r=j;
while(l<r){
swap(s[l],s[r]);
l++;r--;
}
break;
}
}
}
}
}
}
cout<<ans.size()<<endl;
for(int i=0;i<ans.size();i++){
cout<<ans[i].first+1<<" "<<ans[i].second+1<<endl;
}
}
return 0;
}
D1. Optimal Subsequences (Easy Version)
题意:给出一个n个数的序列,有m次询问,每次询问满足条件的子序列的第posj个元素是什么。条件为:所选的子序列有k个元素,并且是所有有k个元素的子序列中所有元素和最大的且字典序最小的。
思路:简单版本可以随便搞,可以先用map记录每个数有多少个,然后对于每一次询问可以直接生成子序列,从最大的数开始选,如果序列剩余需要的个数大于该数的个数,这个数的所有位置都选上,如果剩余的个数小于该数的个数,则只选该数需要的前几个,然后把生成的子序列按照位置排个序,输出第posj个即可。

#include<bits/stdc++.h>
using namespace std;
int num[105];
map<int,vector<int>,greater<int> >tree;
vector<int>null_vec;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,m;
cin>>n;
for(int i=1;i<=n;i++){
cin>>num[i];
if(tree.find(num[i])==tree.end()){
tree[num[i]]=null_vec;
tree[num[i]].push_back(i);
}else{
tree[num[i]].push_back(i);
}
}
cin>>m;
while(m--){
int k,posj,cont=0;
unordered_set<int>need;//num need_num
int special_num,special_num_need;
vector<int>ans;
cin>>k>>posj;
for(auto i=tree.begin();i!=tree.end();i++){
if(cont+(*i).second.size()>=k){
special_num=(*i).first;
special_num_need=k-cont;
break;
}else{
need.insert((*i).first);
cont+=(*i).second.size();
}
}
int cont_small=0;
for(int i=1;i<=n;i++){
if(num[i]==special_num){
if(cont_small<special_num_need){
ans.push_back(num[i]);
cont_small++;
}
}else{
if(need.find(num[i])!=need.end()){
ans.push_back(num[i]);
}
}
}
cout<<ans[posj-1]<<endl;
}
return 0;
}
D2. Optimal Subsequences (Hard Version)
题意:和D1一样,数据量变大了
思路:需要离线化操作,记录下原数组每个数的位置,然后把n个数按照数字从大到小,且数字相同时位置从小到大的顺序排序,这个排好的顺序就是我们生成子序列时选择的顺序。之后把查询按照k从小到大的顺序排序。之后需要树状数组或线段树维护一个[1,n]的前缀和。之后循环依次选择之前排好序的数字,每次将该数字在最一开始的位置标为1,更新前缀和,如果选择的数字等于当前k,则进行查询操作。查询操作是一个二分查找,查找前缀和刚好为posj位置的数,就为答案。更新和查找完所有答案后按提问顺序排序后输出。

#include<bits/stdc++.h>
using namespace std;
struct node{
int k,pos,loc,ans;
}the_query[200005];
struct node_num{
int num,loc;
}the_num[200005];
int tree[800020];
void update(int node,int l,int r,int aim){
if(l==r){
tree[node]=1;
return;
}
int mid=(l+r)>>1;
if(aim<=mid){
update(node<<1,l,mid,aim);
}else{
update(node<<1|1,mid+1,r,aim);
}
tree[node]=tree[node<<1]+tree[node<<1|1];
}
int query(int node,int q_l,int q_r,int l,int r){
if(l>=q_l && r<=q_r){
return tree[node];
}
int mid=(l+r)>>1,x=0,y=0;
if(mid<q_l){
y=query(node<<1|1,q_l,q_r,mid+1,r);
}else if(mid>=q_r){
x=query(node<<1,q_l,q_r,l,mid);
}else{
x=query(node<<1,q_l,q_r,l,mid);
y=query(node<<1|1,q_l,q_r,mid+1,r);
}
return x+y;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,m;
cin>>n;
for(int i=1;i<=n;i++){
cin>>the_num[i].num;
the_num[i].loc=i;
}
cin>>m;
for(int i=1;i<=m;i++){
cin>>the_query[i].k>>the_query[i].pos;
the_query[i].loc=i;
}
sort(the_num+1,the_num+1+n,[](const node_num &a,const node_num &b){
if(a.num==b.num)
return a.loc<b.loc;
else
return a.num>b.num;
});
sort(the_query+1,the_query+1+m,[](const node &a,const node &b){return a.k<b.k;});
for(int i=1,j=1;i<=n;i++){
update(1,1,n,the_num[i].loc);
while(j<=m && i==the_query[j].k){
int l=1,r=n;
while(l<r){
int mid=(l+r)>>1;
int judge=query(1,1,mid,1,n);
if(judge<the_query[j].pos){
l=mid+1;
}else if(judge==the_query[j].pos){
r=mid;
}else{
r=mid-1;
}
}
the_query[j].ans=l;
j++;
}
}
sort(the_query+1,the_query+1+m,[](const node &a,const node &b){return a.loc<b.loc;});
sort(the_num+1,the_num+1+n,[](const node_num &a,const node_num &b){return a.loc<b.loc;});
for(int i=1;i<=m;i++){
cout<<the_num[the_query[i].ans].num<<endl;
}
return 0;
}
F1. Wrong Answer on test 233 (Easy Version) F2. Wrong Answer on test 233 (Hard Version)
题意:一个卷子n个选择题,每个选择题k个选项,你先答了一遍提,然后将所有答案一次往后挪了一题(1题答案对应2题,2题答案对应3题……n题答案对应1题),你知道每个题的正确答案,问转换后得分大于转换前的情况共有多少种可能,答案mod 998244353
思路:数学题,对于答案,我们可以反向考虑,先求得有多少种答题情况转换后分值没有变化,记为ans,所以剩余情况为(nk-ans)个,包含转换后分值变高和转换后分值变低,因为这两种情况是对称的,所以转换后分值变高的情况为 (nk-ans)/2 ,所以问题转换成计算有多少种情况转化后分值无变化。先统计一遍有多少个位置不满足h[i]==h[i+1](注意是个环),记为num,则有n-num个位置满足h[i]==h[i+1],接下来我们考虑,如果分值没变化,那么我有i道题转化后得分由0变1,我就得由i道题转化后得分由1变0,所以是 C(num,i)*C(num-i,i),余下的有num-2i个题要转化后结果一样,为(num-2i)k-2,最后的(n-num)个可以任选就是(n-num)k,所以最终答案为i从0循环到num/2,对C(num,i)*C(num-i,i)*(num-2i)k-2*(n-num)k求累加和即为保持不变的情况个数ans,再用乘法逆元求出(nk-ans)/2 的值即为最终答案。
代码前面大部分为组合数模板,核心代码在main中。

#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
long long table[200005],h[200005];
void cal_table(int n){
table[0]=1;
for(int i=1;i<=n;i++){
table[i]=table[i-1]*i%mod;
}
return;
}
void extend_gcd(long long a,long long b,long long &d,long long &x,long long &y){
if(b==0){
d=a;x=1;y=0;
}else{
extend_gcd(b,a%b,d,x,y);
long long t=x;
x=y;
y=t-(a/b)*(y);
}
return;
}
long long inv(long long a,long long b){
long long x,y,d;
extend_gcd(a,b,d,x,y);
return (d==1)? (x+b)%b : -1;
}
long long C(long long n,long long m){
if(n<0 || m<0 || m>n) return 0;
return table[n]*inv(table[m],mod)%mod*inv(table[n-m],mod)%mod;
}
long long qpow(long long a,long long b){
long long sum=1;
while(b){
if(b&1){
sum=(sum*a)%mod;
b--;
}
b/=2;
a=a*a%mod;
}
return sum;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
long long n,k,num=0,ans=0;
cin>>n>>k;
cal_table(n);
for(int i=1;i<=n;i++){
cin>>h[i];
}
h[n+1]=h[1];
for(int i=2;i<=n+1;i++){
if(h[i-1]!=h[i]) num++;
}
for(long long i=0;2*i<=num;i++){
ans=(ans+(((C(num,i)*C(num-i,i)%mod)*qpow(k-2,num-2*i)%mod)*qpow(k,n-num)%mod))%mod;
}
long long ni=qpow(2,mod-2);
cout<<((qpow(k,n)-ans+mod)*ni)%mod<<endl;
return 0;
}
其实F1还可以用dp去做,但是我不太理解,就没有写出来。
