good problem-3
1.B - Triple Shift
不难发现,只要数组A和B的集合不一样就是No
如果一样,那么一定能从头往后的前N-2项都能匹配上
那么n-1和n项的顺序是关键,因为后两项不能操作
这道题关键是奇偶排列,若A和B排列(先假设A和B无重复数)的奇偶性相同,那么一定能A变为B
因为,一次operation不会改变奇偶性,一次operation交换2个位置,每次交换位置奇偶性改变,2次不变
那么n和n-1项的奇偶性就是整个序列的奇偶性
对于有重复的数,那么一定Yes
因为可以将重复的数改为不同的有先后顺序的数,那么谁在前谁在后是都可以的,那么整个序列的奇偶性是可以改变的
所以通过改变2个重复的数的先后顺序可以得到与B序列相同的奇偶性
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<int,int>
using namespace std;
const int maxn=5000+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,a[maxn],b[maxn],qa[maxn],qb[maxn];
int main(){
n=read();
for(int i=1;i<=n;i++)a[i]=qa[i]=read();
for(int i=1;i<=n;i++)b[i]=qb[i]=read();
sort(qa+1,qa+n+1);sort(qb+1,qb+n+1);
for(int i=1;i<=n;i++)if(qa[i]!=qb[i]){
puts("No");return 0;
}
for(int i=1;i<n;i++)if(qa[i]==qa[i+1]){
puts("Yes");return 0;
}
int inv=0;
for(int i=1;i<n;i++)for(int j=i+1;j<=n;j++){
if(a[i]>a[j])inv^=1;
}
for(int i=1;i<n;i++)for(int j=i+1;j<=n;j++){
if(b[i]>b[j])inv^=1;
}
puts(inv?"No":"Yes");
return 0;
}
2.C - Circular Addition
这道题就是在环上差分
首先考虑普通的非环差分,就是差分数组正的值相加
但环形差分数组不同在于,a[0]=a[n]
所以差分数组d[1]=a[1]-a[n],而普通的d[1]=a[1]-0
这就导致可能a数组整体大于1个值,而差分数组无法体现,使得答案偏小
了解了这些可以看题解具体证明
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#define ll long long
#define pa pair<int,int>
using namespace std;
const int maxn=500000+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
ll n,a[maxn],b[maxn];
int main(){
n=read();ll minn=-inf;
for(int i=1;i<=n;i++)a[i]=a[i+n]=read(),minn=max(a[i],minn);
for(int i=2*n;i>0;i--)a[i]-=a[i-1];
for(int i=1;i<=n*2;i++){
if(a[i]>0)b[i]=b[i-1]+a[i];
else b[i]=b[i-1];
}
ll maxx=b[n];
for(int i=n;i<2*n;i++)maxx=min(maxx,b[i]-b[i-n]);
printf("%lld",max(maxx,minn));
return 0;
}
3.D. Madoka and the Best School in Russia
简单题意就是将X分为多个good数的乘积的方案数至少两个
不妨我们求出X分为多个good数的乘积的方案数
设\(dp_{i,j}表示当前还剩下i没有没除完,j是上一个除的good数\)
预处理出X的因数,因为满足是好数的因数个数最多700多,所以时间复杂度满足条件
第二维是为了防止答案算重,因为多个good数相乘的顺序不同,但个数一样的方案是一样的
dp就类似于完全背包
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#define ll long long
#define pa pair<int,int>
using namespace std;
const int maxn=1000000+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int t,X,D;
bool good(int x,int y){
if(x%y==0 && (x/y)%y)return 1;
return 0;
}
void solve(){
map<pa,int>dp;vector<int>fa;
dp[{X,1}]=1;
for(int i=1;i*i<=X;i++){
if(X%i)continue;
if(good(i,D))fa.push_back(i);
if(X!=i*i && good(X/i,D))fa.push_back(X/i);
}
int ans=0;
while(dp.size()){
auto z=dp.begin();
if(z->first.first==1)ans+=z->second;
for(auto i:fa){
if(z->first.first%i==0 && i>=z->first.second){
dp[{z->first.first/i,i}]=min(dp[{z->first.first/i,i}]+z->second,2);
}
}
dp.erase(z);
}
if(ans>=2)puts("YES");
else puts("NO");
return ;
}
int main(){
t=read();
while(t--){
X=read();D=read();
if(X%D || D==1){puts("NO");continue;}
solve();
}
return 0;
}
4.G - Sqrt
首先操作到1后,一直就为1了,后面就不用考虑了
设\(dp_i\)表示初始位置\(A_1\)为i的方案数
\(dp_i=\sum_{j=1}^{\lfloor \sqrt i \rfloor}dp_j=\sum_{j=1}^{\lfloor \sqrt i \rfloor}\sum_{k=1}^{\lfloor \sqrt j \rfloor}dp_k=\sum_{j=1}^{\lfloor i^{\frac{1}{4}} \rfloor}dp_j\cdot (\lfloor \sqrt i \rfloor -j*j+1)\)
预处理\(i^{\frac{1}{4}}\)的dp值即可
注意sqrt函数的精度问题
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#define ll long long
#define pa pair<int,int>
using namespace std;
const int maxn=1000000+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int t;
ll dp[maxn];
void init(){
dp[1]=1;
for(int i=2;i<=100000;i++){
for(int j=1;j*j<=i;j++)dp[i]+=dp[j];
}
return ;
}
ll issqrt(ll x){
ll y=sqrt(x)-1;
while(y+1<=x/(y+1))y++;
return y;
}
int main(){
t=read();init();
while(t--){
ll x=read(),y=issqrt(x),ans=0;
for(ll i=1;i*i<=y;i++)ans+=dp[i]*(y-i*i+1);
printf("%lld\n",ans);
}
return 0;
}
6.E - Wrapping Chocolate
二维偏序问题
先按把一维l排序,这样就只用考虑一维r问题
对于每个巧克力a[i],把所有大于a[i].l的盒子放入multiset中
lower_bound(a[i].r)找出第一个大于等于a[i].r的盒子
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<int,int>
using namespace std;
const int maxn=4e6+10101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,m;
struct wzq{
int l,r,id;
}a[maxn],b[maxn];
bool cmp(wzq i,wzq j){
if(i.l==j.l)return i.id>j.id;
return i.l>j.l;
}
int main(){
n=read();m=read();
vector<wzq>q;
for(int i=1;i<=n;i++)a[i].l=read();
for(int i=1;i<=n;i++){
a[i].r=read(),a[i].id=0;
q.push_back(a[i]);
}
for(int i=1;i<=m;i++)b[i].l=read();
for(int i=1;i<=m;i++){
b[i].r=read();b[i].id=1;
q.push_back(b[i]);
}
sort(q.begin(),q.end(),cmp);
multiset<int>s;
for(auto i: q){
if(i.id==1)s.insert(i.r);
else {
auto it=s.lower_bound(i.r);
if(it==s.end()){puts("No");return 0;}
s.erase(it);
}
}
puts("Yes");
return 0;
}
7.G - Foreign Friends
题目大意就是求1~n每个点,到\(b_1\) ~ \(b_l\)其中的点的最短距离
1~n每个点有颜色\(a_i\)
点i到\(b_j\)的有距离当且仅当\(a_i \not = a_{b_j}\)
枚举颜色,用二进制来做
先把颜色所表示的二进制第i位为0(或1)的点作为原点跑多源最短路
再把颜色所表示的二进制第i位为1(或0)的点答案更新
这样保证了起点终点的颜色不同
时间复杂度为\(O(log n\cdot nlogn)\)
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=4e6+10101;
const int MOD=998244353;
const ll inf=1ll<<60;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,m,k,l,a[maxn],b[maxn],vis[maxn];
int tot,head[maxn],nx[maxn],to[maxn],c[maxn];
void add(int x,int y,int z){to[++tot]=y;nx[tot]=head[x];head[x]=tot;c[tot]=z;}
ll dis[maxn];
struct wzq{
ll x;int y;
wzq(ll xx,int yy){x=xx;y=yy;}
};
struct cmp{
bool operator()(const wzq &i,const wzq &j){
return i.x>j.x;
}
};
void dj(vector<int> s){
for(int i=1;i<=n;i++)dis[i]=inf,vis[i]=0;
priority_queue<wzq, vector<wzq> , cmp>q;
for(auto i:s){
dis[i]=0;
q.push(wzq(dis[i],i));
}
while(!q.empty()){
int u=q.top().y;q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i;i=nx[i]){
int v=to[i];
if(dis[v]>dis[u]+(ll)c[i]){
dis[v]=dis[u]+(ll)c[i];
q.push(wzq(dis[v],v));
}
}
}
return ;
}
ll ans[maxn];
int main(){
n=read();m=read();k=read();l=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=l;i++)b[i]=read();
for(int i=1;i<=m;i++){
int x=read(),y=read(),z=read();
add(x,y,z);add(y,x,z);
}
for(int i=1;i<=n;i++)ans[i]=inf;
for(int i=0;i<=18;i++)for(int d=0;d<2;d++){
vector<int>s;
for(int j=1;j<=l;j++){
if((a[b[j]]&(1<<i))==d*(1<<i))s.push_back(b[j]);
//或者(a[b[j]]>>i)&1
}
dj(s);
for(int j=1;j<=n;j++){
if((a[j]&(1<<i))==(1-d)*(1<<i))ans[j]=min(ans[j],dis[j]);
}
s.clear();
}
for(int i=1;i<=n;i++){
if(ans[i]==inf)printf("-1 ");
else printf("%lld ",ans[i]);
}
return 0;
}
8.E - Bishop 2
不难发现这道题可以用bfs求最短路
可以运用01bfs
在最短路中,花费的代价为0和1的时候,要求代价最少,那么最短路算法可以解决,但是01bfs用双端队列可以更快且高效的去解决。
01bfs的精髓在于:
有着bfs的特性(一步一步扩展,使得起点到终点的路径最短)
贪心特性:当代价为0的时候,把这一步放在双端队列的队首,代价为1的时候,放在队尾(把不需要代价的步数先计算)。
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=2e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int mm[2001][2001][5],a[2001][2001];
bool vis[2001][2001][5];
int dx[4]={-1,-1,1,1};
int dy[4]={-1,1,-1,1};
int main(){
int n=read();
int x0=read(),y0=read();
int x1=read(),y1=read();
deque< pair<pa,int> >q;
for(int i=1;i<=n;i++){
char ch[maxn];cin>>ch;
for(int j=1;j<=n;j++){
if(ch[j-1]=='.')a[i][j]=0;
else a[i][j]=1;
}
}
memset(mm,0x3f3f3f3f,sizeof(mm));
for(int i=0;i<4;i++){
int x=x0+dx[i],y=y0+dy[i];
if(x>n || x<1 || y>n || y<1 || a[x][y])continue;
if(x==x1 && y==y1){puts("1");return 0;}
q.push_back(make_pair(make_pair(x,y),i));
mm[x][y][i]=1;
}
while(!q.empty()){
int x2=q.front().first.first,y2=q.front().first.second,f=q.front().second;q.pop_front();
if(vis[x2][y2][f])continue;
vis[x2][y2][f]=1;
if(x2==x1 && y2==y1){printf("%d",mm[x2][y2][f]);return 0;}
for(int i=0;i<4;i++){
int x=x2+dx[i],y=y2+dy[i];
if(x>n || x<1 || y>n || y<1 || a[x][y])continue;
if(vis[x][y][i])continue;
if(mm[x][y][i]>mm[x2][y2][f]+(f!=i)){
mm[x][y][i]=mm[x2][y2][f]+(f!=i);
if(i!=f)q.push_back(make_pair(make_pair(x,y),i));
else q.push_front(make_pair(make_pair(x,y),i));
}
}
}
puts("-1");
return 0;
}
9.F - typewriter
考虑容斥,显然,若一段字母同属 𝑥个不同的打字机,若 𝑥 为奇数容斥系数为正,反之为负。关键在于如何枚举。
将集合转化为二进制,枚举打字机的不同组合,找出这些打字机公共的字符,再直接容斥即可。
容斥系数可以枚举几个x试试
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=3e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int book[21][31];
int n,l,dp[maxn];
ll power(ll x,ll y){
ll ans=1;
while(y){
if(y&1)ans=ans*x%MOD;
y>>=1;x=x*x%MOD;
}
return ans;
}
int main(){
n=read();l=read();
for(int i=1;i<=n;i++){
string ch;cin>>ch;
for(int j=0;j<ch.length();j++){
int k=(int)(ch[j]-'a');
book[i][k]=1;
}
}
ll ans=0;
for(int i=1;i<(1<<n);i++){
ll cnt=0;
map<int,bool>mm;
for(int j=1;j<=n;j++){
if(i&(1<<(j-1))){
cnt++;
for(int k=0;k<26;k++){
if(!book[j][k])mm[k]=1;
}
}
}
int now=0;
for(int i=0;i<26;i++){
if(mm[i])continue;
now++;
}
if(cnt&1)cnt=1;
else cnt=-1;
ans+=cnt*power(now,l)%MOD;
}
printf("%lld",(ans%MOD+MOD)%MOD);
return 0;
}
10.孤独的树
首先考虑所有节点都只包含一个质因数,比如2
每个数可以表示为\(2^k\)
不难发现可以用dp做
对于每个节点的操作,要么不除2,要么2除干净,所以设
dp[i][0]表示不操作i节点使得i为根的子树满足要求的最小操作次数
dp[i][1]表示操作i节点使得i为根的子树满足要求的最小操作次数
\(dp[i][0]=\sum_{v\in son(i)}dp[v][1]\)
\(dp[i][1]=cnt+\sum_{v\in son(i)}min(dp[v][0],dp[v][1])\)
cnt为i节点除干净2的次数
考虑原问题:
将全树分解为若干个质因子的连通块
每个联通块内分别树dp求解
如树12-6-2-3-9,分解成2和3作为质因子的连通块:
4-2-2
3-3 3-9
(只把质因子的幂次形式表示出来)
代码实现小技巧:
预先晒出1~maxn每个数的最大质因子,这样就方便对每个点找到指定质因子进行dfs
用map记录当前节点是否被特定质因子除过,防止每个节点都dfs一遍
总时间复杂度为\(O(nlog(val)),val为节点值的最大值\)
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=3e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,a[maxn],val[maxn],is[maxn];
int tot,head[maxn],to[maxn],nx[maxn];
void add(int x,int y){to[++tot]=y;nx[tot]=head[x];head[x]=tot;}
int dp[maxn][2];
//dp[i][0]为不除当前点的质因子,dp[i][1]为除
map<pa,bool>mm;
void dfs(int x,int fa,int now){
dp[x][0]=dp[x][1]=0;
if(a[x]%now)return;
int cnt=0,ji=a[x];
mm[make_pair(x,now)]=1;
while(ji%now==0)dp[x][1]++,ji/=now;
for(int i=head[x];i;i=nx[i]){
int u=to[i];if(u==fa)continue;
dfs(u,x,now);
dp[x][0]+=dp[u][1];
dp[x][1]+=min(dp[u][0],dp[u][1]);
}
return ;
}
int main(){
n=read();for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<n;i++){
int x=read(),y=read();
add(x,y);add(y,x);
}
for(int i=2;i<=maxn;i++){ //筛法求每个数的最大质因子
if(!is[i]){
for(int j=i;j<=maxn;j+=i){
is[j]=1;
val[j]=i;
}
}
}
int ans=0;
for(int i=1;i<=n;i++){
while(a[i]>1){
int p=val[a[i]];
if(!mm[make_pair(i,p)]){
dfs(i,i,p);
ans+=min(dp[i][1],dp[i][0]);
}
while(a[i]%p==0)a[i]/=p;
}
}
printf("%d\n",ans);
return 0;
}