目录
题目地址:https://codeforces.com/contest/1400
A String Similarity
题意:水题
思路:
代码:
#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
int x=0,flag=1; char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
int main()
{
char s[200];
int T=read();
while(T--)
{
int n=read();
scanf("%s",s+1);
REP(i,1,n) putchar(s[i*2-1]);
puts("");
}
return 0;
}
B RPG Protagonist
题意:有两个人,一个可以背 p 单位重量,另一个可以背 f 单位重量;有 c n t s cnt_s cnts 个第一种武器,每个重量为 s,还有 c n t w cnt_w cntw 个第二种武器,每个重量为 w 。问这两个人总共能背走的武器数目最多是多少个?
cnt 的范围是 2e5,其它都是 1e9 。
思路:假设 s<w,然后枚举第一个人拿走 s 的数目,那么第一个人拿走 w 的数目就确定了,然后第二个人就尽可能多的拿走 s。 就是一个贪心的思路。
代码:
#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
int x=0,flag=1; char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
int main()
{
int T=read();
while(T--)
{
LL p=read(),f=read(),cnts=read(),cntw=read();
LL s=read(),w=read();
LL ans=0;
if(s>w) swap(s,w), swap(cnts,cntw);
for(LL i=0;i<=cnts;i++)
{
if(i*s>p) break;
LL tmp=i, cs=cnts-i, cw;
LL j=(p-i*s)/w;
if(j<=cntw) tmp+=j, cw=cntw-j;
else tmp+=cntw, cw=0;
if(s*cs>=f) tmp+=f/s;
else
{
tmp+=cs;
j=(f-cs*s)/w;
if(j<=cw) tmp+=j;
else tmp+=cw;
}
ans=max(ans,tmp);
}
printf("%lld\n",ans);
}
return 0;
}
C Binary String Reconstruction
题意:水题。
思路:首先把 0 的位置构造出来,然后剩下的位置都是 1,然后看是否满足条件就行了。
代码:
#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
int x=0,flag=1; char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=1e5+5;
int s[maxn],w[maxn],n,x;
char t[maxn];
int main()
{
int T=read();
while(T--)
{
scanf("%s",t+1);
x=read();
n=strlen(t+1);
REP(i,1,n) s[i]=t[i]-'0',w[i]=-1;
REP(i,1,n) if(s[i]==0)
{
if(i-x>0) w[i-x]=0;
if(i+x<=n) w[i+x]=0;
}
REP(i,1,n) if(w[i]==-1) w[i]=1;
int flag=1;
REP(i,1,n) if(s[i]==1)
{
int tmp=0;
if(i-x>0 && w[i-x]==1) tmp=1;
if(i+x<=n && w[i+x]==1) tmp=1;
if(tmp==0) {flag=0; break;}
}
if(!flag) puts("-1");
else
{
REP(i,1,n) printf("%d",w[i]);
puts("");
}
}
return 0;
}
D Zigzags
题意:给定一个数组 a,长度为 n( 4 ≤ n ≤ 3000 4\le n \le 3000 4≤n≤3000),统计有多少个四元组 ( i , j , k , l ) (i,j,k,l) (i,j,k,l) ,满足: 1 ≤ i < j < k < l ≤ n 1\le i<j<k<l \le n 1≤i<j<k<l≤n ,并且 a i = a k a_i=a_k ai=ak, a j = a l a_j=a_l aj=al 。
思路:可以看出 n 2 n^2 n2 的时间复杂度和空间复杂度都是可以接受的,所以枚举 j 这一维,然后把 k 和 l 这两维出现次数统计一下(可以哈希),然后对于每个 j,枚举 i,根据后面的统计数据来累加答案。
代码:
#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
int x=0,flag=1; char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
int a[3005],n;
int mp[10000005];
#define TO(x) (x.first*(n+1)+x.second)
int main()
{
int T=read();
while(T--)
{
n=read();
REP(i,1,n) a[i]=read();
REP(i,0,(n+1)*(n+2)) mp[i]=0;
LL ans=0;
for(int i=n-2;i>1;i--)
{
for(int j=i+2;j<=n;j++)
mp[TO(P(a[i+1],a[j]))]++;
for(int k=1;k<i;k++)
ans+=mp[TO(P(a[k],a[i]))];
}
cout<<ans<<endl;
}
return 0;
}
E Clear the Multiset
题意:有一个可重集合,里面有 a 1 a_1 a1 个 1, a 2 a_2 a2 个 2,……, a n a_n an 个 n 。现在有两种操作,第一种是选择一个区间 [ l , r ] [l,r] [l,r] ,然后如果集合里面这个区间内所有数都存在,就把所有数减一;第二种是选择一个数,减去任意的数目。现在给出 n 个数组 a,问至少操作几次,可以删掉集合里面所有元素。
思路:分治。定义一个函数 solve(l, r) 表示处理 l 到 r 区间内的所有数需要最少的次数。可以发现要么整个区间全部用第二种操作,那么次数就是 r-l+1 ;要么找到区间内最小值(minx)以及它的位置(w),然后整个区间的值都减去 minx,那么次数就是 minx+solve(l, w-1)+solve(w+1, r) 。然后递归处理就行了。
复杂度为 O ( n 2 ) O(n^2) O(n2) 。
代码:
#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
int x=0,flag=1; char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
int n,a[5005];
int solve(int l,int r)
{
if(l>r) return 0;
if(l==r) return a[l]>0;
int minx=1e9+5,id;
REP(i,l,r) if(a[i]<minx) minx=a[i],id=i;
int ans1=r-l+1;
if(minx>=ans1) return ans1;
REP(i,l,r) a[i]-=minx;
int ans2=minx+solve(l,id-1)+solve(id+1,r);
return min(ans1,ans2);
}
int main()
{
n=read();
REP(i,1,n) a[i]=read();
cout<<solve(1,n);
return 0;
}
F x-prime Substrings
题意:给定一个只包含 1-9 的字符串 s,然后定义 f(l, r) 表示 s[l,…,r] 的数位和,再定义 s[l,…,r] 是 x-prime 的,当且仅当:
- f(l, r)=x;
- 不存在 [l, r] 内的任何区间 [a, b],使得 f(a, b)!=x 并且 f(a, b) 可以整除 x;
现在问你最少删除 s 中的几个字符,使得删除之后 s 不存在 x-prime 的子串。
1 ≤ ∣ s ∣ ≤ 1000 , x ≤ 20 1\le |s|\le 1000, \ \ x\le 20 1≤∣s∣≤1000, x≤20 。
思路:因为 x 很小,所以可以直接暴力找出所有的 x-prime 的字符串,然后全部放进 Trie树里面,这样问题就变成了:给定一个字符串集合,以及一个字符串 s,你要删掉 s 中尽量少的字符,使得字符集合中任何一个都不是 s 的子串。这就和这道题思路差不多了: DNA repair 。
大致思路就是,把字符串集合建一个 AC自动机,那么问题就变成在 AC自动机上面跑,然后不能走到被标记的结点(就是那些单词以及后缀上有单词的结点)。那么就是个 dp 问题:设 f ( i , j ) f(i,j) f(i,j) 表示 s 的前 i 个字符组成的字符串,匹配到 AC自动机的 j 结点时,最少删除的字符数。然后对于每个 f ( i − 1 , j ) f(i-1,j) f(i−1,j) ,要么不动,也就是删除 s[i],也就是令 f ( i , j ) = m i n ( f ( i , j ) , f ( i − 1 , j ) + 1 ) f(i,j)=min(f(i,j),f(i-1,j)+1) f(i,j)=min(f(i,j),f(i−1,j)+1) ;要么如果可以往 s[i] 转移(假设转移到 x 结点),就令 f ( i , x ) = m i n ( f ( i , x ) , f ( i − 1 , j ) ) f(i,x)=min(f(i,x),f(i-1,j)) f(i,x)=min(f(i,x),f(i−1,j)) 。然后最后取最小值就可以了。
代码:
#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
int x=0,flag=1; char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
struct Trie
{
#define maxC 9
int tot;
int (*t)[maxC],*e,*fail;
Trie(int n)
{
tot=0;
t=new int[n][maxC]();
e=new int[n]();
fail=new int[n]();
}
void insert(char *s)
{
//printf("%s\n",s);
int p=0;
for(int i=0;s[i];i++)
{
int k=s[i]-'1'; // change according to maxC
if(!t[p][k]) t[p][k]=++tot;
p=t[p][k];
}
e[p]++;
}
void get_fail()
{
queue<int> Q;
REP(i,0,maxC-1) if(t[0][i]) Q.push(t[0][i]);
while(!Q.empty())
{
int k=Q.front(); Q.pop();
if(e[fail[k]]) e[k]=e[fail[k]];
REP(i,0,maxC-1)
if(t[k][i]) fail[t[k][i]]=t[fail[k]][i], Q.push(t[k][i]);
else t[k][i]=t[fail[k]][i];
}
}
};
const int maxn=5005,inf=1e8;
int a[25],s[25];
char t[1005],p[1005];
Trie trie(maxn);
int f[1005][maxn];
void get(int k,int sum,int x)
{
if(sum>x) return;
if(sum==x)
{
s[1]=a[1];
REP(i,2,k-1) s[i]=s[i-1]+a[i];
int flag=1;
for(int i=1;i<=k-1;i++)
for(int j=i;j<=k-1;j++)
if(s[j]-s[i-1]!=x && x%(s[j]-s[i-1])==0)
{
flag=0;
break;
}
if(flag)
{
REP(i,1,k-1) t[i]=a[i]+'0';
t[k]=0;
trie.insert(t+1);
}
return;
}
for(int i=1;i<=9;i++) if(sum+i<=x)
a[k]=i,get(k+1,sum+i,x);
}
int main()
{
scanf("%s",p+1);
int x=read();
get(1,0,x);
trie.get_fail();
int n=strlen(p+1),tot=trie.tot;
REP(i,0,n) REP(j,0,tot) f[i][j]=inf;
f[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<=tot;j++)
{
f[i][j]=min(f[i][j],f[i-1][j]+1);
int v=trie.t[j][p[i]-'1'];
if(!trie.e[v]) f[i][v]=min(f[i][v],f[i-1][j]);
}
int ans=inf;
for(int j=0;j<=tot;j++) ans=min(ans,f[n][j]);
cout<<ans;
return 0;
}
G Mercenaries
题意:给出 n( 1 ≤ n ≤ 3 e 5 1\le n\le 3e5 1≤n≤3e5) 个士兵,每个士兵有一个区间 [ L i , R i ] [L_i,R_i] [Li,Ri] ,表示这个士兵所在的集合的大小要处于这个区间之内;然后还有 m( m ≤ 20 m \le 20 m≤20) 组矛盾,表示为 a i a_i ai 和 b i b_i bi 这两个士兵有矛盾,他们不能在同一个集合之中。
你需要计算满足要求的集合数目,取模 998244353 。
思路:首先设一个数组 n u m [ i ] num[i] num[i] 表示可以在大小为 i 的集合内的士兵数目的个数,这个数组可以用差分的方法很快计算出来;然后还是按照从上至下的想法来考虑吧。
我们设 ans[i] 表示当存在 i 组矛盾时的集合数,那么最后的答案就是 a n s [ 0 ] − a n s [ 1 ] + a n s [ 2 ] . . . ans[0]-ans[1]+ans[2]... ans[0]−ans[1]+ans[2]... ,也就是容斥原理的运用。然后对于某个 i 其实不好计算,但是反过来我们枚举所有可能的矛盾情况(总共 2 m 2^m 2m 种),对于每种统计答案之后累加到对应的 ans 中就行了。然后对于某一种矛盾情况,把涉及到的所有士兵拎出来,也就是说这些士兵一定在同一个集合中,所以求一个交集就可以算出满足该条件的集合大小范围,然后我们需要的是在固定这么多个士兵的前提下,一个集合的大小在某个区间的可行的方案数。
有了上面的分析,就可以设 g ( i , j ) g(i,j) g(i,j) 表示确定了 i 个人在集合中,集合大小为 j 的方案数的前缀和,然后有 g ( i , j ) = g ( i , j − 1 ) + C n u m [ j ] − i j − i g(i,j)=g(i,j-1)+C_{num[j]-i}^{j-i} g(i,j)=g(i,j−1)+Cnum[j]−ij−i 。然后就可以做出这道题了。
代码:
#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#include <cstdio>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef std::vector<int> VI;
typedef std::pair<int,int> P;
int read()
{
int x=0,flag=1; char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=3e5+5,N=3e5;
const LL M=998244353;
LL inv[maxn],jie[maxn],n,m,ans[30],g[50][maxn],num[maxn];
int L[maxn],R[maxn],a[maxn],b[maxn];
LL ksm(LL x,LL k)
{
LL ret=1;
while(k)
{
if(k&1) ret=ret*x%M;
x=x*x%M;
k>>=1;
}
return ret;
}
LL C(LL n,LL m)
{
if(n<m) return 0;
if(n==m || !m) return 1;
return jie[n]*inv[m]%M*inv[n-m]%M;
}
void init()
{
jie[0]=1;
REP(i,1,N) jie[i]=jie[i-1]*i%M;
REP(i,0,N) inv[i]=ksm(jie[i],M-2);
}
int main()
{
init();
n=read(),m=read();
REP(i,1,n) L[i]=read(),R[i]=read();
REP(i,0,m-1) a[i]=read(),b[i]=read();
REP(i,1,n) num[L[i]]++,num[R[i]+1]--;
REP(i,1,n) num[i]+=num[i-1];
REP(i,0,m*2) REP(j,1,n)
{
if(i>j) continue;
g[i][j]=g[i][j-1]+C(num[j]-i,j-i);
}
for(int v=0;v<(1<<m);v++)
{
set<int> s;
for(int j=0;j<m;j++)
if((1<<j)&v)
{
s.insert(a[j]);
s.insert(b[j]);
}
int sz=s.size();
int l=1,r=n;
for(int i:s)
{
l=max(l,L[i]);
r=min(r,R[i]);
}
if(l<=r)
{
ans[__builtin_popcount(v)]+=(g[sz][r]-g[sz][l-1]);
}
}
LL x=0;
for(int i=0;i<=m;i++)
{
if(i%2==0) x+=ans[i];
else x-=ans[i];
}
x=(x%M+M)%M;
cout<<x;
return 0;
}