ACM暑期集训第二场
比赛地址
[A]String
题意
给出一个01串,需要把其切分为数段,使得每一段的字典序都是该段01串进行循环变换后最小的。求最小切分次数。
题解
首先先去掉先导1和后导0。然后把串切分为形式为00001111这样的数个串,之后暴力拼接。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
char s[205];
int k[205];
int cnt[205], tot;
int st[205], en[205];
int cmp(int a, int b, int c, int d) {
int len1 = b-a+1;
int len2 = d-c+1;
int minlen = min(len1, len2);
for (int i = 0; i < minlen; i++)
if (s[a+i] > s[c+i]) return -1;
else if (s[a+i] < s[c+i]) return 1;
if (len1 > len2) return -1;
return 0;
}
int main() {
int T; scanf("%d", &T);
while(T--) {
memset(s, 0, sizeof(s));
memset(k, 0, sizeof(k));
memset(cnt, 0, sizeof(cnt));
tot = 0;
scanf("%s", s);
int start = 0;
for (int i = 0; i < strlen(s); i++) {
if ((s[i] == '1' && s[i+1] == '0') || i == strlen(s) - 1) {
++tot;
st[tot] = start;
en[tot] = i;
start = i+1;
}
}
int p = tot;
for (int i = 1; i <= p; i++) {
for (int j = tot-1; j >= 1 ; j--) {
int k = cmp(st[j], en[j], st[j+1], en[j+1]);
if (k != -1) {
en[j] = en[j+1];
for (int l = j+1; l < tot; l++) {
st[l] = st[l+1];
en[l] = en[l+1];
}
tot--;
j--;
}
}
}
for (int i = 1; i <= tot; i++) {
for (int j = st[i]; j <= en[i]; j++) printf("%c", s[j]);
printf(" ");
}
cout << endl;
}
return 0;
}
[B]Irreducible Polynomial
题意
给出一个多项式,判断是否在有理数域上可约。
题解
根据高代知识,次数高于2次必定可约。2次判断是否有根,有根则可约。1次及0次必定不可约。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int main() {
int T;
scanf("%d", &T);
while(T--) {
int n;
scanf("%d", &n);
if (n >= 3) {
int flag = 1;
int k; scanf("%d", &k);
for (int i = 1; i <= n; i++) {
scanf("%d", &k);
if (k) flag = 0;
}
printf("No\n");
continue;
} else if (n <= 1) {
int k; for (int i = 1; i <= n+1; i++) scanf("%d", &k);
printf("Yes\n");
continue;
} else {
long long int a, b, c;
cin >> a >> b >> c;
long long int b2 = b*b;
long long int ac = 4 * a * c;
if (ac <= 0) {
printf("No\n");
continue;
} else if (b2 >= ac) {
printf("No\n");
continue;
} else {
printf("Yes\n");
continue;
}
}
}
return 0;
}
[C]Governing sand
题意
有n堆树,每堆树有一个高度,树的总数,和砍去一棵树的代价。现在需要砍掉一些树,使得最高的树占树的总数的一半以上。求最小砍树代价。
题解
枚举最高的树的高度,比它高的树全部砍掉,如果数目仍达不到一半,则再砍一些低的树。这时对砍一棵树的代价二分答案,计算砍多少价值及一下的树可以满足条件。每当处理完一个高度的树后,按照代价把其扔进一个序列中。因为代价至多为200,所以可以使用分块维护。每10价值为1块。故时间复杂度为O(20nlogC)
需要注意一些细节,以及long long一定要开全。
代码
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
#define N 100010
#define l(x) (x<<1)
#define r(x) ((x<<1)+1)
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
int n,i,j,p;
LL z[N],co[N],Co[N],s[N],S[N],tot[N],Tot[N];
LL Ans,cnt,ss,low,high,mid,sum,q;
map <int,LL> M,id;
struct Tree{
int h;
LL c,p;
}t[N];
inline LL Min(LL a,LL b){return (a<b)?a:b;}
inline int read(){
int p=0; char c=getchar();
while (c<48||c>57) c=getchar();
while (c>=48&&c<=57) p=(p<<1)+(p<<3)+c-48,c=getchar();
return p;
}
inline bool cmp(Tree a,Tree b){return a.h<b.h;}
inline void Put(int x){
int i=0;
s[t[x].c]+=t[x].p; co[t[x].c]+=t[x].c*t[x].p;
i=(t[x].c/20)+1;
S[i]+=t[x].p; Co[i]+=t[x].c*t[x].p;
}
int main(){
while (~scanf("%d",&n)){
M.clear(); id.clear();
memset(t,0,sizeof(t)); memset(z,0,sizeof(z));
memset(s,0,sizeof(s)); memset(S,0,sizeof(S));
memset(co,0,sizeof(co)); memset(Co,0,sizeof(Co));
memset(tot,0,sizeof(tot)); memset(Tot,0,sizeof(Tot));
Ans=(LL)INF*INF; cnt=0; Ans=2*Ans;
for (i=1;i<=n;i++)
scanf("%d%lld%lld",&t[i].h,&t[i].c,&t[i].p);
sort(t+1,t+1+n,cmp);
for (i=1;i<=n;i++) M[t[i].h]+=t[i].p;
for (i=1;i<=n;i++){
if (t[i].h==t[i-1].h){
z[z[0]]+=t[i].c*t[i].p; tot[z[0]]+=t[i].p;
continue;
}
z[++z[0]]=t[i].c*t[i].p; tot[z[0]]+=t[i].p; id[t[i].h]=z[0];
}
for (i=2;i<=z[0];i++) z[i]+=z[i-1];
for (i=1;i<=z[0];i++) Tot[i]=Tot[i-1]+tot[i];
for (i=1;i<=n;i++){
if (t[i].h==t[i-1].h) continue;
for (j=i-1;t[j].h==t[i-1].h&&j;j--) Put(j);
sum=0; cnt=0;
cnt=z[z[0]]-z[id[t[i].h]];
sum=tot[id[t[i].h]];
if (Tot[id[t[i].h]]<=2*sum-1) {
Ans=Min(Ans,cnt); continue;
}
sum=Tot[id[t[i].h]]-(2*sum-1);
q=0;
for (j=1;j<=200;){
p=(j/20)+1;
if (q>=sum) break;
if (q+S[p]<=sum){
q+=S[p]; cnt+=Co[p]; j=p*20;
} else {
if (q+s[j]>=sum){
cnt+=(sum-q)*(LL)j;
break;
} else {
q+=s[j]; cnt+=co[j]; j++;
}
}
}
Ans=Min(Ans,cnt);
}
printf("%lld\n",Ans);
}
return 0;
}
[D]Number
题意
给出一个数p,构造一个n位数能整除p
题解
签到题
代码
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int main() {
int n, p;
scanf("%d%d", &n, &p);
int len = 0;
int q = p;
while(q) {
len++;
q /= 10;
}
if (n < len) {
printf("T_T");
} else if (n == len) {
printf("%d", p);
} else {
printf("%d", p);
for (int i = 1; i <= n-len; i++) printf("0");
}
return 0;
}
[E]Find the median
题意
有n个操作,每个操作给出一个区间[l,r]。初始时有一个空序列,每进行一次操作,则把l,l+1,…,r添加到序列中。每次操作完毕后求该序列的中位数。
题解
考虑到区间范围高达1e9,所以需要对区间进行离散化。离散化完毕后建立线段树,线段树上每个节点维护一段原区间。每次先进行区间加1的操作,树上的节点记录下这段区间中一共有多少个数。然后算出中位数的位置,再进行区间查询,查到叶子节点后,根据这段区间被覆盖的次数进而计算出答案。
需要注意的是为了防止离散后的区间相互拼接到一起导致WA,需要把区间变成左闭右开再离散化。因为这道题卡内存,所以线段树不能存左右端点是多少。具体细节见代码。
代码
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
#define N 400010
#define l(x) (x<<1)
#define r(x) ((x<<1)+1)
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
int n,i,cnt;
LL a1,a2,b1,b2,c1,c2,m1,m2,Ans,L,R,sum;
int x[N],y[N],l[N],r[N],c[N*2],C[N*2];
map <int,int> M,M2;
struct Tree{
int len,lz;
LL sum;
}t[N*8];
inline int Abs(int x){return (x<0)?-x:x;}
inline void Swap(int &a,int &b){a^=b^=a^=b;}
inline int Min(int a,int b){return (a<b)?a:b;}
inline int Max(int a,int b){return (a>b)?a:b;}
inline int read(){
int p=0; char c=getchar();
while (c<48||c>57) c=getchar();
while (c>=48&&c<=57) p=(p<<1)+(p<<3)+c-48,c=getchar();
return p;
}
inline void Maketree(int x,int low,int high){
int mid=(low+high)>>1;
if (low==high){
t[x].len=C[low+1]-C[low];
return;
}
Maketree(l(x),low,mid); Maketree(r(x),mid+1,high);
t[x].len=t[l(x)].len+t[r(x)].len;
}
inline void Push(int x){
LL p=t[x].lz;
t[l(x)].lz+=p; t[r(x)].lz+=p;
t[l(x)].sum+=(LL)p*t[l(x)].len;
t[r(x)].sum+=(LL)p*t[r(x)].len;
t[x].lz=0;
}
inline void Add(int x,int l,int r,int low,int high){
int mid=(l+r)>>1;
if (l==low&&r==high){
t[x].sum+=t[x].len; t[x].lz++; return;
}
if (t[x].lz) Push(x);
if (high<=mid) Add(l(x),l,mid,low,high);
else if (low>mid) Add(r(x),mid+1,r,low,high);
else Add(l(x),l,mid,low,mid),Add(r(x),mid+1,r,mid+1,high);
t[x].sum=t[l(x)].sum+t[r(x)].sum;
}
inline LL Query(int x,int l,int r,LL y){
int mid=(l+r)>>1;
LL p=0;
if (l==r) {
p=t[x].sum/(LL)t[x].len;
return (LL)C[l]+(y-1)/p;
}
if (t[x].lz) Push(x);
return (y<=t[l(x)].sum)?Query(l(x),l,mid,y):Query(r(x),mid+1,r,y-t[l(x)].sum);
}
int main(){
scanf("%d",&n);
scanf("%d%d%lld%lld%lld%lld",&x[1],&x[2],&a1,&b1,&c1,&m1);
scanf("%d%d%lld%lld%lld%lld",&y[1],&y[2],&a2,&b2,&c2,&m2);
for (i=3;i<=n;i++){
x[i]=((LL)a1*x[i-1])%m1;
x[i]=(x[i]+(LL)b1*x[i-2]+c1)%m1;
y[i]=((LL)a2*y[i-1])%m2;
y[i]=(y[i]+(LL)b2*y[i-2]+c2)%m2;
}
for (i=1;i<=n;i++) l[i]=Min(x[i],y[i])+1,r[i]=Max(x[i],y[i])+2;
for (i=1;i<=n;i++) c[++c[0]]=l[i],c[++c[0]]=r[i];
sort(c+1,c+1+c[0]);
cnt=0;
for (i=1;i<=c[0];i++)
if (!M[c[i]]) M[c[i]]=++cnt,C[cnt]=c[i];
cnt--;
Maketree(1,1,cnt);
for (i=1;i<=n;i++){
L=M[l[i]]; R=M[r[i]]-1; sum+=(LL)(r[i]-l[i]);
Add(1,1,cnt,L,R); Ans=Query(1,1,cnt,(sum+1)/2);
printf("%d\n",Ans);
}
return 0;
}
[I]Chessboard
题意
构造一个k*k的棋盘,每个格子上填一个不小于m正整数,使得不同行不同列的k个格子中的数加起来相同,且不超过n。求方案数。
题解
首先抛开限制,先考虑构造一个k*k的棋盘,不同行不同列的格子中的数加起来总和为T的方案数。先定义两种矩阵:
A_i:第i行为1,其余行为0;B_i:第i列为1,其余列为0
则符合题意的矩阵C一定满足以下形式:
C=\sum_{i=1}^ka_iA_i+\sum_{i=1}^kb_iB_i
其中的系数非负,正确性显然。
因为要保证总和为T,则有
\sum_{i=1}^ka_i+\sum_{i=1}^kb_i=T
则方案数为
C_{T+2k-1}^{2k-1}
但是这些选数方案有重叠,当a全为正数时,让a全部减一,b全部加一,得到的矩阵相同,但是被多计数了一次。所以真正的方案数为
f(k,T)=C_{T+2k-1}^{2k-1}-C_{T+k-1}^{2k-1}
注意后一项可能无意义,意味着a不能全为正数,这种情况要排除掉。
现在考虑加上限制后的情况,如果构造k*k的矩阵,每个数不小于m,总和为T,可以去掉限制,将总和变为T-k*m,再使用上面的式子计算即可。最终答案为:
\sum_{k=1}^{INF}\sum_{T=mk}^{n}f(k,T-mk)
代码
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 4010
#define MOD 998244353
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
int n,m,i,j,T;
LL Ans;
LL ml[N],nml[N];
inline int Abs(int x){return (x<0)?-x:x;}
inline void Swap(int &a,int &b){a^=b^=a^=b;}
inline int Min(int a,int b){return (a<b)?a:b;}
inline double Max(double a,double b){return (a>b)?a:b;}
inline int read(){
int p=0; char c=getchar();
while (c<48||c>57) c=getchar();
while (c>=48&&c<=57) p=(p<<1)+(p<<3)+c-48,c=getchar();
return p;
}
inline LL Mi(LL x,LL y){
LL p=x,t=1,Res=1;
for (;t<=y;(t&y)?Res=(Res*p)%MOD:0,p=(p*p)%MOD,t<<=1);
return Res;
}
inline void Ready(){
int i=0;
ml[0]=ml[1]=1;
for (i=2;i<=4000;i++) ml[i]=((LL)ml[i-1]*i)%MOD;
for (i=0;i<=4000;i++) nml[i]=Mi(ml[i],MOD-2);
}
inline LL f(int k,int s){
LL p=1,q=1;
p=ml[s+2*k-1]; q=ml[s+k-1];
p=(p*nml[2*k-1])%MOD; q=(q*nml[2*k-1])%MOD;
p=(p*nml[s])%MOD;
if (s-k>=0) q=(q*nml[s-k])%MOD; else q=0;
return (p-q+MOD)%MOD;
}
int main(){
T=read();
Ready();
while (T--){
scanf("%d%d",&n,&m);
Ans=0;
for (i=1;i<=INF;i++){
if (m*i>n) break;
for (j=m*i;j<=n;j++){
Ans=(Ans+f(i,j-m*i))%MOD;
}
}
printf("%lld\n",Ans);
}
return 0;
}
[J]A+B problem
题意
定义f(x)=x的各位倒序排列值。求f(f(a)+f(b))
题解
从名字就可以看出是签到题
代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#define N 200010
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
int T;
LL a,b,c,d;
LL z[100];
inline int Abs(int x){return (x<0)?-x:x;}
inline int Min(int a,int b){return (a<b)?a:b;}
inline int Max(int a,int b){return (a>b)?a:b;}
inline int read(){
int p=0; char c=getchar();
while (c<48||c>57) c=getchar();
while (c>=48&&c<=57) p=(p<<1)+(p<<3)+c-48,c=getchar();
return p;
}
LL f(LL x){
int i=0;
LL y=0;
z[0]=0;
while (x){
z[++z[0]]=x%10; x/=10;
}
for (i=1;i<=z[0];i++) y=y*10+z[i];
return y;
}
int main(){
scanf("%d",&T);
while (T--){
scanf("%lld%lld",&a,&b);
c=f(a); d=f(b);
printf("%lld\n",f(c+d));
}
return 0;
}
总结&吐槽
这次比赛总共做出5题,达到了平均水平。差一点做出了E,最后时间不够没能调完。
中间因为各种奇怪的问题吃了很多罚时,比如B题没有整明白出题人对可约的定义,A题上来口胡了几个不靠谱的做法,之后才证明有漏洞。C题因为缺省源没开long long多WA了一发。。。
希望接下来能够保持状态,少吃罚时,提升一下过题速度。
没有抽到抱枕不开心
实名羡慕一队拿到的抱枕QWQ
也希望在下一场比赛中可以拿到抱枕有更好的发挥。
本文地址: ACM暑期集训第二场