【ACM夏训】综合训练赛

综合训练赛一

1. 捣蛋的ly(字符串+贪心)

原题链接

Solution

采用贪心策略来做题,找到峰值,即递减序列的第一个:

  • 输入字符串与要删的数
  • 处理前,特判特殊情况
  • 找递减序列的第一个,如果找到了,则将这个位置标记,并且将位置 w w w赋值为 j j j​,退出内层循环
  • 将位置w以后的数组元素全部向前移动一位
  • 输出,并且特判前导0

完整程序

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
typedef long long ll;
string st;
ll s,p,w,len;
int main()
{
    cin>>st>>s;
    len=st.size();
    /* 特判特殊情况 */
    if(st[0]=='1'&&st[1]=='0'&&s==1)
    {
        cout<<'0';
        return 0;
    }
    for(int i=1;i<=s;i++)
    {
        for(int j=0;j<=len-i+1;j++)
        {
            if(st[j]>st[j+1]||j==len-i+1)
            {	/* 记录下递减序列的第一个的位置,并打上标记 */
                w=j;
                st[j]='*';
                break;
            }
        }
        /* 将标记元素之后的元素向前移动 */
        for(int j=w+1;j<len;j++) st[j-1]=st[j],st[j]='*';
    }
    for(int i=0;i<len-s;i++)
    {
        if(st[i]!='0') p=1,cout<<st[i];
        /* 如果是前导0,则不输出 */
        if(st[i]=='0'&&p==1) cout<<st[i];
    }
    return 0;
}

2. 圣诞节(动态规划+树形dp)

原题链接

Solution

经典的树形DP,进入正题:

  • f a [ i ] [ j ] fa[i][j] fa[i][j]表示 i i i这个点上色为 j j j是方案数(从下往上/从子节点向父节点更新

  • 即对于所有初始节点, f a [ i ] [ 1 ] , f a [ i ] [ 2 ] , f a [ i ] [ 3 ] fa[i][1],fa[i][2],fa[i][3] fa[i][1],fa[i][2],fa[i][3]都为1

  • 当某个节点被指定上色后,那么该节点另外两种颜色的方案数为0.

  • 例如:当点 x x x被指定上色2时, f [ x ] [ 0 ] = 0 , f [ x ] [ 3 ] = 0 f[x][0]=0,f[x][3]=0 f[x][0]=0,f[x][3]=0(因为无法上色1和3)

  • 对于每个节点,因为不能于子节点上色相同,即:
    f a [ s o n ] [ i ] = f a [ s o n ] [ i ] ∗ ( ( f a [ e [ i ] . v ] [ j ] + f a [ e [ i ] . v ] [ k ] ) % m o d ) % m o d ( i = 0 , j = 1 , k = 2 ; i = 1 , j = 0 , k = 2 ; i = 2 , j = 0 , k = 1 ) fa[son][i]=fa[son][i]*((fa[e[i].v][j]+fa[e[i].v][k])\%mod)\%mod(i=0,j=1,k=2;i=1,j=0,k=2;i=2,j=0,k=1) fa[son][i]=fa[son][i]((fa[e[i].v][j]+fa[e[i].v][k])%mod)%mod(i=0,j=1,k=2;i=1,j=0,k=2;i=2,j=0,k=1)

  • 数据范围方面:取模和开long long

完整程序

#include <iostream>
using namespace std;
#define ll long long
const int mod = 1e9 + 7;
const int MAX_N = 2e5 + 100;
int n,k,x,y,head[MAX_N],tot;
ll fa[MAX_N][3];
struct node{
    int v,next;
}e[MAX_N<<1];
/* 速读 */
inline int read(){
    int ret=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-f;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0') ret=ret*10+ch-'0',ch=getchar();
    return ret*f;
}
/* 建边 */
inline void add_edge(int u,int v){
    e[++tot].next=head[u];
    head[u]=tot;
    e[tot].v=v;
}
inline void dfs(int son,int father){
    /* 当某个节点被指定上色后,那么该节点另外两种颜色的方案数为0 */
    for(int i=0;i<3;i++){
        if(fa[son][i]){
            for(int j=0;j<i;j++)
                fa[son][j]=0;
            break;
        }
        fa[son][i]=1;
    }
    /* 链式前向星遍历 */
    for(int i=head[son];i;i=e[i].next){
        if(e[i].v!=father){
            dfs(e[i].v,son);
            fa[son][0]=fa[son][0]*((fa[e[i].v][1]+fa[e[i].v][2])%mod)%mod;
            fa[son][1]=fa[son][1]*((fa[e[i].v][0]+fa[e[i].v][2])%mod)%mod;
            fa[son][2]=fa[son][2]*((fa[e[i].v][0]+fa[e[i].v][1])%mod)%mod;
        }
    }
}
int main(){
    n=read();k=read();
    for(int i=1;i<n;i++) x=read(),y=read(),add_edge(x,y),add_edge(y,x);
    for(int i=1;i<=k;i++) x=read(),y=read()-1,fa[x][y]=1;
    dfs(1,0);
    printf("%lld",(fa[1][0]+fa[1][1]+fa[1][2])%mod);
    return 0;
}

3. 种田(动态规划)

原题链接

Solution

  • ( i , j ) (i,j) (i,j)为右下角结点,当 a [ i ] [ j ] = = 1 a[i][j]==1 a[i][j]==1时,检查左、上、左上角的结点是否为0,以 f [ i ] [ j ] f[i][j] f[i][j]来表示以 ( i , j ) (i,j) (i,j)​为结点的最大正方形的边长。

  • 先对数据进行预处理,将数组元素手动赋值为1

  • 标记不能通过的点的坐标对应的元素为0

完整程序

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1005;
int a[N][N];
int dp[N][N];
int n,m,p,q;
int ans=0;
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            a[i][j]=1;
        }
    }
    for(int i=1;i<=m;i++){
        scanf("%d %d",&p,&q);
        a[p][q]=0;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(a[i][j]==1){
                dp[i][j]=min(min(dp[i][j-1],dp[i-1][j]),dp[i-1][j-1])+1;
            }
            ans=max(ans,dp[i][j]);
        }
    }
    printf("%d",ans);
    return 0;
}

4. 解密童话2

原题链接

Solution

  • 定义一个sum数组来表示该节点有多少个字符串经过

完整程序

#include <iostream>
#include <cstring>
using namespace std;
int n,m,k,ch[500001][3],tot=1,bo[500001],sum[500001],x;
bool p[500001];
inline int read(){
    int data=0,w=1; char ch=0;
    while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0' && ch<='9') data=data*10+ch-'0',ch=getchar();
    return data*w;
}
void add(bool p[]) {
    int u=1;
    for(int i=1; i<=k; i++) {
        int c=p[i];
        if(ch[u][c]==-1) ch[u][c]=++tot;
        u=ch[u][c];
        sum[u]++;
    }
    bo[u]++;
}
int find(bool p[]) {
    int u=1;
    int res=0;
    for(int i=1; i<=k; i++) {
        int c=p[i];
        if(ch[u][c]==-1) return res;
        u=ch[u][c];
        res+=bo[u];
    }
    return res-bo[u]+sum[u];
}
int main() {
    int x;
    m=read();
    n=read();
    memset(ch,-1,sizeof ch);
    for(int i=1; i<=m; i++) {
        k=read();
        for(int j=1; j<=k; j++) {
            p[j]=read();
        }
        add(p);
    }
    for(int i=1; i<=n; i++) {
        k=read();
        for(int j=1; j<=k; j++) {
            p[j]=read();
        }
        printf("%d\n",find(p));
    }
}

5. 落魄的wyh比赛版

原题链接

Solution

  • 考虑到 a [ i ] a[i] a[i]​的取值范围,数组空间应该申请为1e6
  • 离线算法之莫队算法普通模板,对if-else中的判断稍作改进

完整程序

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 1000100;
int n,q;
int a[N],vis[N],s[N],now,ans[N];
int l,r;
struct node{
    int l,r,id;
    bool operator <(const node b) const{
        return s[l]==s[b.l]?r<b.r:s[l]<s[b.l];
    }
}Q[N];
/* 速读 */
inline int read(){
    int ret=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if (ch=='-') f=-f;ch=getchar();}
    while (ch<='9'&&ch>='0') ret=ret*10+ch-'0',ch=getchar();
    return ret*f;
}
inline void del(int x){vis[a[x]]--;if (!vis[a[x]]) now--;}
inline void add(int x){vis[a[x]]++;if (vis[a[x]]==1) now++;}
int main(){
    n=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    q=read();
    for(int i=1;i<=q;i++){
        Q[i].l=read();Q[i].r=read();Q[i].id=i;
    }
    int si=sqrt(n);
    int num=ceil((double)n/si);
    for(int i=1;i<=num;i++)
        for(int j=(i-1)*si+1;j<=i*si;j++)
            s[j]=i;
    sort(Q+1,Q+q+1);
    for(int i=1;i<=q;i++){
        while(l<Q[i].l) del(l++);
        while(l>Q[i].l) add(--l);
        while(r<Q[i].r) add(++r);
        while(r>Q[i].r) del(r--);
        if(now==Q[i].r-Q[i].l+1) ans[Q[i].id]=1;
        else ans[Q[i].id]=now;
    }
    for(int i=1;i<=q;i++){
        if(ans[i]==1) printf("%d\n",Q[i].r-Q[i].l+1);
        else printf("%d\n",ans[i]);
    }
    return 0;
}

6. 落魄的wyh数据增强版

原题链接

Solution

  1. 按r排序一下
  2. 树状数组tree[j]维护从1到j不同数字的个数有多少个
  3. 然后用前缀和的思想(tree[r]-tree[l-1])

完整程序

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAX_N 1000100
int ans[MAX_N],sta[MAX_N],tree[MAX_N],n,q,B[MAX_N];
struct node{
    int l,r;
    int pos;
    bool operator <(const node &b)const{
        return r<b.r;
    }
}query[MAX_N];
int lowbit(int i){
    return i&(-i);
}
void add(int i,int now){
    while(i<=n){
        tree[i]+=now;
        i+=lowbit(i);
    }
}
int sum(int i){
    int ans=0;
    while(i!=0){
        ans+=tree[i];
        i-=lowbit(i);
    }
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&sta[i]);
    scanf("%d",&q);
    for(int i=1;i<=q;i++){
        scanf("%d %d",&query[i].l,&query[i].r);
        query[i].pos=i;
    }
    sort(query+1,query+1+q);
    int nex=1;
    for(int i=1;i<=q;i++){
        for(int j=nex;j<=query[i].r;j++){
            if(B[sta[j]]) add(B[sta[j]],-1);
            add(j,1);
            B[sta[j]]=j;
        }
        nex=query[i].r+1;
        ans[query[i].pos]=sum(query[i].r)-sum(query[i].l-1);
    }
    for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
    return 0;
}

7. 魔法少女(异或和)

原题链接

Solution

  • a   x o r   b < = a + b a\ xor \ b <=a+b a xor b<=a+b​,即分组越少,值越小

完整程序

#include<iostream>
using namespace std;
int main(){
    int n,a,ans;
    scanf("%d",&n);
    scanf("%d",&ans);
    for(int i=2;i<=n;i++){
        scanf("%d",&a);
        ans^=a;
    }
    cout<<ans;
    return 0;
}

8. bbh surprises wyh

原题链接

Solution

可以操作的基元只有 ( a , b ) , ( a , − b ) , ( b , a ) , ( b , − a ) (a,b),(a,-b),(b,a),(b,-a) (a,b),(a,b),(b,a),(b,a),因为加上 ( − a , − b ) (-a,-b) (a,b)​相当于减去 ( a , b ) (a,b) (a,b),其他的同理。

那么问题转化为了找到整数 c , d , e , f c,d,e,f c,d,e,f,使得:
c ( a , b ) + d ( a , − b ) + e ( b , a ) + f ( b , − a ) = ( x , y ) c(a,b)+d(a,-b)+e(b,a)+f(b,-a)=(x,y) c(a,b)+d(a,b)+e(b,a)+f(b,a)=(x,y)
化简上式有:
( c + d ) a + ( e + f ) b = x (c+d)a+(e+f)b=x (c+d)a+(e+f)b=x

( e − f ) a + ( c − d ) b = y (e-f)a+(c-d)b=y (ef)a+(cd)b=y

根据裴蜀定理,方程 a x + b y = c ax+by=c ax+by=c​有整数解 ( a , b ) (a,b) (a,b)​,当且仅当 g c d ( x , y ) ∣ c gcd(x,y)|c gcd(x,y)c​成立

P ( x , y , c ) P(x,y,c) P(x,y,c)为真表示上式成立,那么问题进一步转化为 P ( a , b , x ) & & P ( a , b , y ) P(a,b,x)\&\&P(a,b,y) P(a,b,x)&&P(a,b,y)​为真,且保证 c , d , e , f c,d,e,f c,d,e,f为整数。

要讨论 c , d , e , f c,d,e,f c,d,e,f的奇偶性,先讨论 c + d c+d c+d, c − d c-d cd, e + f e+f e+f, e − f e-f ef的奇偶性。

  1. ( c + d ) = ( e + f ) = ( e − f ) = ( c − d ) = 0 ( m o d   2 ) (c+d)=(e+f)=(e-f)=(c-d)=0(mod\ 2) (c+d)=(e+f)=(ef)=(cd)=0(mod 2)​时,我们发现 c + d 2 , e + f 2 , e − f 2 , c − d 2 \frac{c+d}{2},\frac{e+f}{2},\frac{e-f}{2},\frac{c-d}{2} 2c+d,2e+f,2ef,2cd的取值范围为整数。
    即有:
    c + d 2 a + e + f 2 b = x 2 , e − f 2 a + c − d 2 b = y 2 \frac{c+d}{2}a+\frac{e+f}{2}b=\frac{x}{2},\frac{e-f}{2}a+\frac{c-d}{2}b=\frac{y}{2} 2c+da+2e+fb=2x,2efa+2cdb=2y
    根据裴蜀定理有, P ( a , b , x 2 ) & & P ( a , b , y 2 ) P(a,b,\frac{x}{2})\&\&P(a,b,\frac{y}{2}) P(a,b,2x)&&P(a,b,2y),即 P ( 2 a , 2 b , x ) & & P ( 2 a , 2 b , y ) P(2a,2b,x)\&\&P(2a,2b,y) P(2a,2b,x)&&P(2a,2b,y)​为真。

  2. c + d c+d c+d, e + f e+f e+f, e − f e-f ef, c − d c-d cd中有某一组或者两组都是奇数,以 c + d c+d c+d, c − d c-d cd为例:

    ( c + d ) a + ( e + f ) b = x , ( e − f ) a + ( c − d ) b = y (c+d)a+(e+f)b=x,(e-f)a+(c-d)b=y (c+d)a+(e+f)b=x,(ef)a+(cd)b=y
    得:
    ( c + d + 1 ) a + ( e + f ) b = x + a , ( e − f ) a + ( c − d + 1 ) b = y + b (c+d+1)a+(e+f)b=x+a,(e-f)a+(c-d+1)b=y+b (c+d+1)a+(e+f)b=x+a,(ef)a+(cd+1)b=y+b
    回到偶数的情况 P ( 2 a , 2 b , x + a ) & & P ( 2 a , 2 b , y + b ) P(2a,2b,x+a)\&\&P(2a,2b,y+b) P(2a,2b,x+a)&&P(2a,2b,y+b)为真。

  3. 综上所述,当:

    P ( 2 a , 2 b , x ) & & P ( 2 a , 2 b , y ) P(2a,2b,x)\&\&P(2a,2b,y) P(2a,2b,x)&&P(2a,2b,y)为真。

    P ( 2 a , 2 b , x + a ) & & P ( 2 a , 2 b , y + b ) P(2a,2b,x+a)\&\&P(2a,2b,y+b) P(2a,2b,x+a)&&P(2a,2b,y+b)​为真。

    P ( 2 a , 2 b , x + b ) & & P ( 2 a , 2 b , y + a ) P(2a,2b,x+b)\&\&P(2a,2b,y+a) P(2a,2b,x+b)&&P(2a,2b,y+a)​​为真。

    P ( 2 a , 2 b , x + a + b ) & & P ( 2 a , 2 b , y + a + b ) P(2a,2b,x+a+b)\&\&P(2a,2b,y+a+b) P(2a,2b,x+a+b)&&P(2a,2b,y+a+b)​​为真。

即方程 h ( a , b , x , y ) h(a,b,x,y) h(a,b,x,y)为真。​

完整程序

#include <iostream>
using namespace std;
typedef long long ll;
ll gcd(ll x,ll y){
    return y ? gcd(y,x%y) : x;
}
bool check(ll x,ll y,ll z){
    return !(z%gcd(x,y));
}
int t;
ll a,b,x,y;
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%lld %lld %lld %lld",&a,&b,&x,&y);
        int flag=(check(2*a,2*b,x)&&check(2*a,2*b,y))||(check(2*a,2*b,x+a)&&check(2*a,2*b,y+b))||(check(2*a,2*b,x+b)&&check(2*a,2*b,y+a))||(check(2*a,2*b,x+a+b)&&check(2*a,2*b,y+a+b));
        if(flag)
            printf("Y\n");
        else printf("N\n");
    }
    return 0;
}

9. 地下通道(Kruskal)

原题链接

Solution

基于并查集的卡鲁斯卡尔算法

完整程序

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2005;
struct node{
    int u,v,dis;
    bool operator <(const node&b)const{
        return dis<b.dis;
    }
}e[N*1000];
/* 数组要开大点 */
int n,c,tot;
int px[N],py[N];
int fa[N];
inline void add_edge(int u,int v,int w){
    e[++tot].u=u;
    e[tot].v=v;
    e[tot].dis=w;
}
inline int add(int x){
    if(fa[x]!=x) fa[x]=add(fa[x]);
    return fa[x];
}
inline void kruskal(){
    int u,v,dis;
    int cnt=0,ans=0;
    for(int i=1;i<=n;i++){
        fa[i]=i;
    }
    for(int i=1;i<=tot;i++){
        u=e[i].u,v=e[i].v;
        u=add(u),v=add(v);
        if(u!=v){
            fa[u]=v;
            ans+=e[i].dis;
            cnt++;
        }
        if(cnt==n-1) break;
    }
    if(cnt==n-1) printf("%d",ans);
    else puts("-1");
}
inline int cal(int i,int j){
    return (px[i]-px[j])*(px[i]-px[j])+(py[i]-py[j])*(py[i]-py[j]);
}
int main(){
    scanf("%d %d",&n,&c);
    /* 预处理 */
    for(int i=1;i<=n;i++){
        scanf("%d %d",&px[i],&py[i]);
        for(int j=1;j<i;j++){
            int w=cal(i,j);
            if(w>=c){
                add_edge(i,j,w);
            }
        }
    }
    sort(e+1,e+1+tot);
    kruskal();
    return 0;
}

10. 还有少女们

原题链接

Solution

直接使用邻接矩阵存储。并且记录每个点入度。

然后依次删掉入度为0的点,每删一个更新与这个点相邻的点的入度。然后再删掉入度为0的点,一直重复,直到没有入度为0的点或删完为止。

完整程序

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 505;
int n,m,x,k,graph[N][N],in[N],q[10*N],l=1,r,maxx;
bool vis[N];    /* 记录有无这个点 */
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d %d",&x,&m);
        vis[x]=true;
        for(int j=1;j<=m;j++){
            scanf("%d",&k);
            graph[x][k]=1;
            in[k]++;
        }
        maxx=max(maxx,x);   /* 最大点的编号 */
    }
    /* 拓扑排序 */
    for(int i=0;i<=maxx;i++)
        if(in[i]==0&&vis[i]) q[++r]=i;  /* 将入度为0的点放入队列 */
    int ans=0;
    while(ans<n){
        if(l>r){    /* 不可再删 */
            printf("%d",n-ans);
            return 0;
        }
        int cmd=q[l++]; /* 删点 */
        for(int i=0;i<=maxx;i++){
            if(graph[cmd][i]&&vis[i]){
                in[i]--;
                if(in[i]==0&&vis[i]) q[++r]=i;
            }
        }
        ans++;  /* 更新已删点的个数 */
    }
    if(ans==n) printf("YES");
    return 0;
}

11. 生蚝算账

原题链接

参考题解

线段树解法

12. 好学的xym

原题链接

参考题解

数论 构造

13. 伊雷娜与《妮可的冒险谭》

原题链接

Solution

  • 读入数据先按照花色、数字进行排序和去重。
  • 枚举每一个点作为同花顺的右端点。
  • 找到左侧可以连成同花顺的第一张牌,计算同花顺长度,更新最长的长度。
  • 计算剩余的牌的数量,输出即可。

先排序,使相同花色的在一起,并且满足相同花色的数字从小到大排列,然后可以用queue维护,遇到新的不同的花色就清空队列,否则就删除队首不满足的,然后将当前这个枚举到的结尾插入队列,然后每次取个最大的size,最后求得最大的不用更换的牌数(然后用总排数减去就是最终答案了)

完整程序

#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
int n,ans,cnt;
struct node{
    int l,r;
    bool operator<(const node&b)const{
        return l==b.l? r<b.r : l<b.l;
    }
}a[N],b[N];
queue<int> q;
void clear(){
    while(!q.empty()) q.pop();
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d %d",&a[i].l,&a[i].r);
    sort(a+1,a+n+1);
    /* 离散化 */
    for(int i=1;i<=n;i++){
        if(a[i].l==a[i-1].l&&a[i].r==a[i-1].r) continue;
        b[++cnt]=a[i];
    }
    for(int i=1;i<=cnt;i++){
        if(b[i].l!=b[i-1].l) clear();
        while(q.size()&&b[i].r-q.front()>=n) q.pop();
        q.push(b[i].r);
        ans=max(ans,(int)q.size());
    }
    ans=(n-ans);
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeGLMath

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值