目录
A - Move Right
读题,将数字向右移动一位,前导为0即可.
#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
using namespace std;
using ll = long long;
const int N = 5e5 + 10, mod = 998244353;
int main()
{
string str;
cin>>str;
str="0"+str;
for(int i=0;i<4;i++)
cout<<str[i];
return 0;
}
B - Unique Nicknames
该题要求取的绰号和任意下方的人的姓,名都不一样.标记去重判断即可.
#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
using namespace std;
using ll = long long;
const int N = 5e5 + 10, mod = 998244353;
map<string,int>vis;
string a[103],b[103];
int main()
{
int n,flag=0;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i]>>b[i];
if(a[i]==b[i])
vis[a[i]]++;
else
vis[a[i]]++,vis[b[i]]++;
}
for(int i=0;i<n;i++)
{
if(vis[a[i]]>=2&&vis[b[i]]>=2)
{
cout<<"No";
return 0;
}
}
cout<<"Yes";
return 0;
}
C - 1 2 1 3 1 2 1
这个题意思是可以把x变为字符串x-1 x x-1的形式,并且一直转化到两段不能转化为止,很容易就联想到大法师(DFS)
#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
using namespace std;
using ll = long long;
const int N = 5e5 + 10, mod = 998244353;
void dfs(int x)
{
if(x==1)
{
cout<<1;
return ;
}
dfs(x-1);
cout<<" "<<x<<" ";
dfs(x-1);
}
int main()
{
int n;
cin>>n;
dfs(n);
return 0;
}
D - Cylinder
该题题意是给你一定的操作次数,并且含有两种操作,一种是向数组的右侧插入c个值为x的球,另一种操作是在数组左侧取出c个球,并且求出他们的值的和.
可以采用双端队列进行模拟操作.向右插入的操作就插入一个节点,这个节点储存三个值,左右的端点和这个区间的球的值.而第二种操作直接进行模拟,我取出双端队列队首的区间,看能不能取完,不能去玩就继续取,能取完就直接取完计算,因为储存了区间的左右端点和值,那么可以直接用区间长度和值相乘取得和.
#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#define int long long
using namespace std;
const int N = 5e5 + 10, mod = 998244353;
struct node
{
int l,r,x;
};
deque<node>qu;
signed main()
{
int q,op,c,x,l=1,sum=0;
cin>>q;
while(q--)
{
cin>>op;
if(op==1)
{
cin>>x>>c;
qu.push_back({l,l+c-1,x});
l=l+c;
}
else if(op==2)
{
sum=0;
cin>>c;
node bef;
while(c>0)
{
bef=qu.front();
if(c>bef.r-bef.l+1)
{
sum+=(bef.r-bef.l+1)*bef.x;
qu.pop_front();
c-=bef.r-bef.l+1;
}
else if(c<=bef.r-bef.l+1)
{
sum+=c*bef.x;
qu.pop_front();
bef.l+=c;
qu.push_front(bef);
c=0;
}
}
cout<<sum<<"\n";
}
}
return 0;
}
E - Max Min
题意:
给你一个长度为n的数组,并且给定最大值x和最小值y.要求求出这个数组中有多少个连续区间满足:包含y和x,并且这个区间内的值都是小于x大于y的.输出满足上面两种条件的连续子区间的数量即可
这两种都难得想,需要一定的思维,我是看了大佬题解才理解的
方法1:状压dp
状压dp的形式为 f [ i ] [ 1/0 ] [ 1/0 ];第一位i的意思为以i结尾的子区间,后面两维分别表示这些区间是否满足包含y和x.这个式子的意思就是看以i结尾的区间内是否满足包含y,x的子区间最大数目.详细的意思可以看代码中的注解.
#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#define int long long
using namespace std;
using ll = long long;
const int N = 5e5 + 10, mod = 998244353;
int n,l,r;
int a[200005];
int f[200005][3][3];
signed main()
{
int ans=0;
cin>>n>>r>>l;
for(int i=1;i<=n;i++)
cin>>a[i];
f[0][0][0]=0;
for(int i=1;i<=n;i++)
{
if(a[i]<l||a[i]>r)
continue;
//当前遍历到的数字不符合要求
int ll=(a[i]==l),rr=(a[i]==r);
//ll,rr意为当前的数字是否为y或者x
f[i][ll][rr]+=1;
//这个是以i结尾并且只选了当前这个数字a[i]的情况,只选了一个数,所以+1
for(int j=0;j<2;j++)
for(int k=0;k<2;k++)
f[i][ll|j][rr|k]+=f[i-1][j][k];
//遍历所有状态,看把当前遍历到的数字i插入之前的所有情况的子区间中,进行状态转移
//例如f[i-1][1][0]意为以i-1结尾且包含有y但是不包含x的区间个数
//假设当前遍历到了x,那么ll=0,rr=1;
//那么就会转移为f[i][1][1]+=f[i-1][1][0];
//因为在选上当前的a[i]后状态可以如上发生转移
ans+=f[i][1][1];
//加上符合条件的
}
cout<<ans;
return 0;
}
方法2:容斥定理
首先在了解了基础的容斥的情况下.我们可以计算出区间呢你所有数字的范围都在[ y , x ]之前的子区间个数.比较好算
int get_num(int l,int r)
{
int ans=0,temp=0;
for(int i=0;i<n;i++)
{
if(a[i]>r||a[i]<l)
{
ans+=(temp+1)*temp/2;
temp=0;
}
else
temp++;
}
if(temp)
ans+=(temp+1)*temp/2;
return ans;
}
只要遍历一遍,看连续的在范围内的数字有多少之后,分别以第一个数第二个数...为子区间的左端点计算子区间个数即可.满足了在[ y , x ]的范围内之后,只要满足一定包含x,y即可.这里可以画一张图理解:
三个大圆圈的含义已经写出来了,[y+1,+inf]和[y,x]的相交区域a的意思为[y+1,x],[-inf,x-1]和[y,x]的相交区域b的意思为[y,x-1],那么[y,x]和a不相交的区域的意思就是在[y,x]的范围内包含有y的子区间的集合,而b和[y,x]不相交的区域就是在[y,x]范围内包含有x的子区间的集合,那么可以推出,[y,x]和a,b不相交的区域的即为所求,即在[y,x]范围内但是必定包含x,y的子区间的集合.所以我们就可以用之前的那个方法求出这三块的面积,并且用[y,x]的集合去减去a,b.但是这里会发现有一块c重复减去了两次,所以我们要加上c,c即为在[y,x]的范围内[y+1,x-1]的集合子区间的个数,加上即可.
#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#define int long long
using namespace std;
const int N = 5e5 + 10, mod = 998244353;
int n,x,y;
int a[200005];
int get_num(int l,int r)
{
int ans=0,temp=0;
for(int i=0;i<n;i++)
{
if(a[i]>r||a[i]<l)
{
ans+=(temp+1)*temp/2;
temp=0;
}
else
temp++;
}
if(temp)
ans+=(temp+1)*temp/2;
return ans;
}
//求区间个数
signed main()
{
cin>>n>>x>>y;
for(int i=0;i<n;i++)
cin>>a[i];
cout<<get_num(y,x)-get_num(y,x-1)-get_num(y+1,x)+get_num(y+1,x-1);
//容斥核心
return 0;
}
F - Cards
有n张牌,每个牌有两面,正反两面分别为p,q,且p,q数组都是1-n的全排列.问有多少种选法可以让选出的牌(正反两面都算)包含有1-n的全排列.
这个题涉及到的其实是在数组上面建图的问题
拿例子来说
3
1 2 3
2 1 3
将正反两面的数字练起来可以建立以下的图:
我们将每个集合的方案数相乘即可得到方案数.而每个集合的方案数的计算方法可以由枚举找出规律来,f[1]=1,f[2]=3,f[n]=f[n-1]+f[n-2].(可以枚举试试) .那么用并查集分开集合后计算集合方案相乘即可:
#include <map>
#include <set>
#include <queue>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#define int long long
using namespace std;
using ll = long long;
const int N = 2e5 + 10, mod = 998244353;
int p[N],q[N],f[N],du[N],fa[N];
int find(int x)
{
if(x==fa[x])
return x;
else
return fa[x]=find(fa[x]);
}
void merge(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy)
fa[fx]=fy;
return ;
}
signed main()
{
int n;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&p[i]);
for(int i=1;i<=n;i++)
scanf("%lld",&q[i]);
f[1]=1,f[2]=3;
fa[1]=1,fa[2]=2;
for(int i=3;i<=n;i++)
f[i]=(f[i-1]+f[i-2])%mod,fa[i]=i;
for(int i=1;i<=n;i++)
merge(p[i],q[i]);
for(int i=1;i<=n;i++)
du[find(i)]++;
int ans=1;
for(int i=1;i<=n;i++)
{
if(du[i])
ans=ans*f[du[i]]%mod;
}
printf("%lld",ans);
return 0;
}