第一场
T1 「CEOI2017」Palindromic Partitions
给出一个只包含小写字母字符串,要求你将它划分成尽可能多的小块,使得这些小块的字符串构成回文序列。例如:对于字符串 abcab,将他分成 ab+c+ab 或者 abcab 就是构成回文串的划分方法,abc+ab 则不是。对于任何串,显然不做任何拆分得到的就是回文序列,该回文序列长度为 1 。现在求拆分得到的回文序列长度的最大值。
贪心的思路,有回文串就拆分,哈希判断是否相同
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,mode=9999991;
int T,len,ans;
int tp1,tp2,idx,hsh[N];
char s[N];
void init(){
hsh['a'-1]=1;
for(int i='a';i<='z';i++){
hsh[i]=(hsh[i-1]*131+1314520)%mode;
}
}
int ksm(int a,int b,int c){
int ans=1;
while(b>0){
if(b&1){
ans=(ans*a)%c;
}
b>>=1;
a=(a*a)%c;
}
return ans;
}
signed main(){
init();
scanf("%lld",&T);
while(T--){
scanf("%s",s+1);
len=strlen(s+1);
int l=1,r=len;
tp1=0,tp2=0,idx=0,ans=0;
while(l<r){
tp1=(tp1*131+hsh[s[l]])%mode;
tp2=(tp2+hsh[s[r]]*ksm(131,idx,mode))%mode;
idx++;
if(tp1==tp2){
ans+=2;
tp1=0,tp2=0,idx=0;
}
l++,r--;
}
if(idx||len%2)ans++;
printf("%lld\n",ans);
}
return 0;
}
T2 何老板线
何老板买了一条地铁线!没错!一整条!何老板命名为何老板线,何老板就开始运营这条线路。何老板这条地铁线上一共有m+1个站点, 编号为0到m. 何老板地铁线上运行的列车一共有m种, 第i种地铁的停车间隔为i, 所有列车的始发站都是0号站。例如停车间隔为3的列车会依次经过0,3,6,9号站点。何老板新开的地铁线为了挣人气,一共在售n种纪念品,第i种纪念品可以在编号的地铁站[l_i,r_i]买到。现在果老师去乘坐何老板地铁线的列车,果老师想知道对于每种停车间隔的列车分别能购买到的纪念品种数是多少?
可以整除分块或者数据结构
数据结构的话,按照区间长度排序,小于区间长度的都可以取到,大于区间长度的放进树状数组里询问后加上就是ans
#include<bits/stdc++.h>
using namespace std;
const int N=6e5+5;
int n,m;
struct nnode{
int l,r,len;
}a[N];
bool cmp(nnode x1,nnode x2){
return x1.len<x2.len;
}
int bit[N];
void add(int i,int x){
for(;i<=m;i+=i&(-i)){
bit[i]+=x;
}
}
int ask(int i){
int ans=0;
for(;i;i-=i&(-i)){
ans+=bit[i];
}
return ans;
}
void modify(int x,int y,int w){
add(x,w);
add(y+1,-w);
}
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i].l,&a[i].r);
a[i].len=a[i].r-a[i].l+1;
}
sort(a+1,a+1+n,cmp);
int now=1,i=1;
while(i<=n){
while(now<=a[i].len){
int ans=n-i+1;
for(int j=1;j<=m/now;j++){
ans+=ask(j*now);
}
printf("%d\n",ans);
now++;
}
modify(a[i].l,a[i].r,1);
i++;
}
while(now<=m){
int ans=n-i+1;
for(int j=1;j<=m/now;j++){
ans+=ask(j*now);
}
printf("%d\n",ans);
now++;
}
return 0;
}
T3 袋鼠
有一个园子,里面有 n 个草丛排成一排,标号1∼n,有一个袋鼠,从 s 出发,每次跳一步跳到一个其他的草丛,经过每个草丛恰好一次,最终到达 t。显然他会跳跃n−1次为了不被人类发现,袋鼠每次跳跃的方向必须与前一次不同。
具体地,如果他现在在 now,他是从 prev 跳跃一次到达 now 的,然后他跳跃一次到达next:
-
那么如果 prev<now,就必须有 next<now;
-
如果 now<prev,就必须有 now<next。
问从 s 到 t 的方案数模 10^9+7 的结果。两个路线不同,当且仅当草丛被访问的顺序不同。保证至少有一种方案初始时可以往任意方向跳。
奇怪的动态dp,设状态f[i][j]表示前i个数分j段的方法数。它在状态转移中只会出现倒V,或者M型
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e3+5,mode=1e9+7;
int n,s,t,ans;
int f[N][N];
int mod(int x,int p){
return x>=p?x%p:x;
}
signed main(){
scanf("%lld%lld%lld",&n,&s,&t);
f[1][1]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<=i;j++){
if(i==s||i==t)f[i][j]=mod(f[i-1][j-1]+f[i-1][j],mode);
else f[i][j]=mod(f[i-1][j-1]*(j-(i>s)-(i>t))+f[i-1][j+1]*j,mode);
}
}
printf("%lld\n",f[n][1]);
return 0;
}
第二场
T1 有内涵的数字
如果一个整数k可以写成 的形式,其中p,q都是质数且p<q,则k是一个有内涵的数字。
请你统计N以内有多少个有内涵的数字。
欧拉筛+暴力(比二分快)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,ac=1e6;
int n,nn,ans;
int prime[N],is_prime[N],cnt;
void euler(){
for(int i=2;i<=nn;i++){
if(!is_prime[i]){
prime[++cnt]=i;
int temp=i*i*i;
for(int j=1;j<=cnt&&prime[j]<i&&temp*prime[j]<=n;j++){
ans++;
}
}
for(int j=1;j<=cnt&&i*prime[j]<=nn;j++){
is_prime[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
signed main(){
scanf("%lld",&n);
nn=min(n,ac);
euler();
printf("%lld\n",ans);
return 0;
}
T2 最大GCD
给出n组询问,每次问A<=x<=B, C<=y<=D时gcd(x, y)的最大值。
1<=N<=1000
1<=A<=B<=10^9,1<=C<=D<=10^9
整除分块,只用b,d取min,因为包含了a,c,统计最大的ans
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=0x3f3f3f3f3f3f3f3fLL;
int n,a,b,c,d,ans;
bool check(int x){
if(b/x>(a-1)/x&&d/x>(c-1)/x)return true;
return false;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
if(b>d){
swap(a,c);
swap(b,d);
}
if(b>=c){
printf("%lld\n",b);
}else{
ans=1;
int i=1;
while(true){
int tb=b<i?inf:b/(b/i);
int td=d<i?inf:d/(d/i);
int temp=min(tb,td);
if(temp==inf)break;
if(check(temp)){
ans=max(ans,temp);
}
i=temp+1;
}
printf("%lld\n",ans);
}
}
return 0;
}
T3 「CEOI2017」Sure Bet
现在有n个 A 类灯泡和n个 B 类灯泡,每个灯泡都有各自的权值。
我们将这些灯泡分为n组,每组包含一个来自 A 类的灯泡和一个来自 B 类的灯泡。
你可以从中选取任意个灯泡,每选取一个灯泡需要花费1的代价。
在你选取完之后,系统会随机在 A 类和 B 类中选择一个类型,并点亮那一类的所有灯泡。你选取的每个点亮的灯泡会给你带来等于它权值的收益。
现在请你合理选取灯泡,以最大化可能的最小收益。你只需要求出来这个收益即可。
贪心,哪边拖后腿了选哪边,统计最大的收益
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
const double inf=1314520;
int n,cnta,cntb;
double a[N],b[N],wa,wb,ans=-inf;
bool cmp(double x1,double x2){
return x1>x2;
}
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lf%lf",&a[i],&b[i]);
}
sort(a+1,a+1+n,cmp);
sort(b+1,b+1+n,cmp);
while(true){
if(wa-cnta-cntb<wb-cnta-cntb&&cnta+1<=n){
wa+=a[++cnta];
}else if(cntb+1<=n){
wb+=b[++cntb];
}else{
break;
}
ans=max(ans,min(wa-cnta-cntb,wb-cnta-cntb));
}
printf("%0.4f\n",ans>0?ans:0);
}
第三场
T1 Best Cow Line
Farmer John 打算带领 NN()头奶牛参加一年一度的”全美农场主大奖赛“。在这场比赛中,每个参赛者必须让他的奶牛排成一列,然后带领这些奶牛从裁判面前依此走过。今年,竞赛委员会在接受报名时,采用了一种新的登记规则:取每头奶牛名字的首字母,按照它们在队伍中的次序排成一列。将所有队伍的名字按字典序升序排序,从而得到出场顺序。FJ 由于事务繁忙,他希望能够尽早出场。因此他决定重排队列。他的调整方式是这样的:每次,他从原队列的首端或尾端牵出一头奶牛,将她安排到新队列尾部。重复这一操作直到所有奶牛都插入新队列为止。现在请你帮 FJ 算出按照上面这种方法能排出的字典序最小的队列。
哪边小输出哪边,相同的话比较相同部分后一个的大小输出,二分加哈希
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+5;
int n;
char tp[5],a[N];
int temp[3][N],seed[3],mode[3];
int ha[3][N],hb[3][N];
int mod(int x,int p){
return (x%p+p)%p;
}
void init(){
seed[1]=131,seed[2]=520;
mode[1]=1e9+7,mode[2]=1e9+9;
for(int i=1;i<=2;i++){
temp[i][0]=1;
for(int j=1;j<=n;j++){
temp[i][j]=mod(temp[i][j-1]*seed[i],mode[i]);
}
}
for(int i=1;i<=2;i++){
for(int j=1;j<=n;j++){
ha[i][j]=mod(ha[i][j-1]*seed[i]+a[j],mode[i]);
}
}
for(int i=1;i<=2;i++){
for(int j=n;j>=1;j--){
hb[i][j]=mod(hb[i][j+1]*seed[i]+a[j],mode[i]);
}
}
}
int check(int fr,int to,int len){
int tp1=mod(ha[1][fr+len-1]-ha[1][fr-1]*temp[1][len],mode[1]);
int tp2=mod(hb[1][to-len+1]-hb[1][to+1]*temp[1][len],mode[1]);
int tp3=mod(ha[2][fr+len-1]-ha[2][fr-1]*temp[2][len],mode[2]);
int tp4=mod(hb[2][to-len+1]-hb[2][to+1]*temp[2][len],mode[2]);
if(tp1==tp2&&tp3==tp4){
return true;
}
return false;
}
int find(int fr,int to){
int l=1,r=(to-fr+1)>>1;
while(l<r){
int mid=(l+r+1)>>1;
if(check(fr,to,mid)){
l=mid;
}else{
r=mid-1;
}
}
return l;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%s",tp);
a[i]=tp[0];
}
init();
int l=1,r=n;
while(l<=r){
if(a[l]<a[r]){
printf("%c",a[l]);
l++;
}else if(a[l]>a[r]){
printf("%c",a[r]);
r--;
}else if(a[l]==a[r]){
if(l==r){
printf("%c",a[l]);
l++;
break;
}else{
int len=find(l,r);
if(a[l+len]<a[r-len]){
printf("%c",a[l]);
l++;
}else{
printf("%c",a[r]);
r--;
}
}
}
}
return 0;
}
T2 卡牌操作
有n张卡片在桌上一字排开,每张卡片上有两个数,第i张卡片上,正面的数为a[i],反面的数为b[i]。现在,有m个熊孩子来破坏你的卡片了!
第i个熊孩子会交换c[i]和d[i]两个位置上的卡片。
每个熊孩子捣乱后,你都需要判断,通过任意翻转卡片(把正面变为反面或把反面变成正面,但不能改变卡片的位置),能否让卡片正面上的数从左到右单调不降。
线段树,直接合并
#include<bits/stdc++.h>
#define lson tree[now].ls
#define rson tree[now].rs
using namespace std;
const int N=1e6+5,NN=2e6+5,inf=1e8;
int n,m;
int ta,tb,a[N],b[N],x,y;
int tot;
struct node{
int a,b,ls,rs,aw,bw,af,bf;
}tree[NN];
void putUp(int now){
if(tree[rson].af){
if(tree[lson].af&&a[tree[rson].a]>=tree[lson].aw){
tree[now].af=true;
tree[now].aw=tree[rson].aw;
}
if(tree[lson].bf&&a[tree[rson].a]>=tree[lson].bw){
tree[now].bf=true;
tree[now].bw=tree[rson].aw;
}
}
if(tree[rson].bf){
if(tree[lson].af&&b[tree[rson].a]>=tree[lson].aw){
tree[now].af=true;
tree[now].aw=min(tree[now].aw,tree[rson].bw);
}
if(tree[lson].bf&&b[tree[rson].a]>=tree[lson].bw){
tree[now].bf=true;
tree[now].bw=min(tree[now].bw,tree[rson].bw);
}
}
}
void make(int l,int r){
int now=++tot;
tree[now].a=l;
tree[now].b=r;
tree[now].af=false;
tree[now].bf=false;
tree[now].aw=inf;
tree[now].bw=inf;
if(l==r){
tree[now].af=true;
tree[now].bf=true;
tree[now].aw=a[l];
tree[now].bw=b[l];
return;
}
int mid=(l+r)>>1;
tree[now].ls=tot+1;
make(l,mid);
tree[now].rs=tot+1;
make(mid+1,r);
putUp(now);
}
void change(int now,int v){
tree[now].af=false;
tree[now].bf=false;
tree[now].aw=inf;
tree[now].bw=inf;
if(tree[now].a==tree[now].b){
tree[now].af=true;
tree[now].bf=true;
tree[now].aw=a[v];
tree[now].bw=b[v];
return;
}
int mid=(tree[now].a+tree[now].b)>>1;
if(v<=mid)change(lson,v);
else change(rson,v);
putUp(now);
}
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&ta,&tb);
a[i]=min(ta,tb);
b[i]=max(ta,tb);
}
make(1,n);
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
swap(a[x],a[y]);
swap(b[x],b[y]);
change(1,x);
change(1,y);
if(tree[1].af||tree[1].bf){
printf("TAK\n");
}else{
printf("NIE\n");
}
}
return 0;
}
T3 最短的路
在美丽富饶的NKLJ中学,有一位名叫希望的天之骄子。一天,他从NKLJ步行来到沙坪坝磁器口跟摊主玩游戏。游戏规则如下:给出一张n个点m条边的无向图,求点s到点t的最短路。
第一行两个整数n ,m。
第2行到第m+1行,每行三个整数 ,表示点u到点v有一条长度为的双向边。
第m+2行输入两个整数s,t表示起点和终点。
数据保证没有重边或自环。
(洛谷给希望推荐了这道题,他却没有看题解qwq)
所以他现在不会写