2019陕西省赛(重温经典)

2019陕西省赛(重温经典)

导语

为省赛练手,但是省赛延期了(悲)

涉及的知识点

搜索,思维,字典树,数学

链接:2019陕西省赛[Cloned]

题目

B

题目大意:一个总规模为 n × m n×m n×m的矩阵,矩阵上的每个位置有其下一位置的信息,询问是否存在一种解法从某一点出发,使得整个矩阵的每个位置都被访问到,如果越界或者遇到重复访问位置的解法被认为失败

思路:首先,如果想要由一个位置出发能访问到其他的所有位置,那么有两种情况可以满足:存在唯一的位置无其他位置可到达,且由该位置出发能遍历矩阵从任意位置出发,遍历矩阵之后回到该位置,那么思路就很好总结出来了,寻找入度为0的位置有多少个,多个则无解,单个则遍历查看是否能访问整个矩阵,没有则任意挑一点遍历,查看是否能访问整个矩阵

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int t,n,m,cnt,a[maxn],deg[maxn],start,num;
bool vis[maxn];
string s[maxn];
bool DFS(int x,int ans) {//遍历路径,查看是否能遍历整个矩阵
    if(ans==n*m)return 1;
    if(x==-1||vis[x])return 0;
    vis[x]=1;
    return DFS(a[x],ans+1);
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>n>>m;
        for(int i=1; i<=n; i++)cin >>s[i];
        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++) {
                int in,nt=(i-1)*m+j,x=i,y=j;//nt为转换为一维数组之后的编号,x,y是坐标
                cin >>in;
                switch(s[i][j-1]) {//处理方向
                case 'u':
                    nt-=in*m;
                    x-=in;
                    break;
                case 'd':
                    nt+=in*m;
                    x+=in;
                    break;
                case 'l':
                    nt-=in;
                    y-=in;
                    break;
                default:
                    nt+=in;
                    y+=in;
                    break;
                }
                if(x>n||y>m||x<1||y<1)nt=-1;//坐标不合法
                if(nt!=-1)deg[nt]++;//记录入度
                a[++cnt]=nt;//将原数组转换成一维数组方便处理
            }
        for(int i=1; i<=n*m; i++)//遍历查找度为0的节点
            if(deg[i]==0) {
                num++;
                start=i;
            }
        if(num>1)
            cout <<"No\n";
        else {
            num==1?start=start:start=1;//挑出发点
            DFS(start,1)?cout <<"Yes\n":cout<<"No\n";
        }
        memset(vis,0,sizeof(vis));//清空相关变量
        memset(deg,0,sizeof(deg));
        start=cnt=num=0;
    }
    return 0;
}

C

题目大意:给出一个由0,6,8,9组成的字符串,选取字符串的一个子串进行翻转得到新串,询问能够得到多少个不同的新串

思路:假设每个数都不一样,那么可以简单的得到总方案数为: ( 1 + n ) × n / 2 (1+n)×n/2 (1+n)×n/2,加上自身为 ( 1 + n ) × n / 2 + 1 (1+n)×n/2+1 (1+n)×n/2+1,现在需要去掉所有方案中重复的数,那么翻转之后存在的情况有只有开头末位是0-0,8-8,6-9,9-6,对于0-0和8-8的情况,被重复计算的方案个数为 C 0 的 个 数 2 + C 8 的 个 数 2 C_{0的个数}^2+C_{8的个数}^2 C02+C82,对于6-9和9-6的情况,重复的方案数为 n u m 6 × n u m 9 num_6×num_9 num6×num9,最后特判如果全是6或9的情况,这种情况下是不能回到自身的

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
int t,ans[5];
string s;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>s;
        int len=s.length();
        for(int i=0; i<len; i++)
            switch(s[i]) {
            case '0':
                ans[0]++;
                break;
            case '6':
                ans[1]++;
                break;
            case '8':
                ans[2]++;
                break;
            case '9':
                ans[3]++;
                break;
            }
        ll res=1ll*len*(len+1)/2+1;//全排列
        res-=1ll*ans[0]*(ans[0]+1)/2+1ll*ans[1]*ans[3]+1ll*ans[2]*(ans[2]+1)/2;//去重
        if(ans[1]==len||ans[3]==len)res--;//无法回到自己
        cout <<res<<endl;
        memset(ans,0,sizeof(ans));
    }
    return 0;
}

E

题目大意:给出一个01串,保证至少一个1,现在规定一个区间长度L,每次可以将长度L的区间都变成0,现在询问如果至多操作s次能使得整个01串都为0,L的最小值是多少

思路:首先有两个边界值,即理想情况,L的最小值不会小于1的个数除以总次数,最大值不会超过字符串长度除以次数,在这个区间内贪心的去尝试即可,因为本题常数很大,所以整体的复杂度其实是 O ( n 2 / C ) O(n^2/C) O(n2/C),C很大

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+20;
char str[maxn];
int flag[maxn];
bool judge(int num,int k,int n) {//贪心放置
    int base=k;//操作次数
    bool mark=true;
    for(int i=0; i<n;) {
        while(str[i]=='0')
            i++;
        i+=num;
        base--;
        if(i>=n)break;
        if(base<=0) {//操作次数不足
            if(flag[i]!=0)//后缀还有1
                mark=false;
            break;
        }
    }
    return mark;
}
inline int cal(int x,int k) {
    return x%k?x/k+1:x/k;
}
void solve() {
    int n,k;
    scanf("%d%d",&n,&k);
    scanf("%s",str);
    int coun=0;
    for(int i=0; i<n; i++)//统计1的个数
        if(str[i]=='1')
            coun++;
    flag[n]=0;
    for(int i=n-1; i>=0; i--)//计算后缀1的个数
        str[i]=='1'?flag[i]=flag[i+1]+1: flag[i]=flag[i+1];
    if(coun<=k) {//1的个数小于操作次数
        printf("1\n");
        return ;
    }
    int cmin=cal(coun,k),cmax=cal(n,k);//获得两个边界值
    bool mark=0;
    int ans=cmin;
    for(int i=cmin; i<cmax; i++) {//判断区间内每个值
        mark=judge(i,k,n);
        if(mark) {
            ans=i;
            break;
        }
    }
    printf("%d\n",mark?ans:cmax);
}
int main() {
    int t;
    scanf("%d",&t);
    while(t--)
        solve();
    return 0;
}

F

题目大意:定义一个 k k k小时时钟,规定每天被分成 k k k小时,第 i i i小时被称为 i − 1 i-1 i1点,如果现在是 x x x点,那么1小时后就是x+1点( 0 ≤ x < k − 1 0\le x\lt k-1 0x<k1),如果现在是 k − 1 k-1 k1点,那么1小时后就是0点,现在是 x x x点,经过 y y y小时后是 z z z点,求 k k k的值

思路:将原问题提取出来,等价于求方程 ( x + y ) % k = z (x+y)\%k=z (x+y)%k=z k k k的值,对所有情况分类,如果 x + y = z x+y=z x+y=z,那么 k ≥ z + 1 & k > x k\ge z+1\&k>x kz+1&k>x,如果 x + y < z x+y<z x+y<z,无解,如果 x + y > z x+y>z x+y>z,那么就有方程 x + y = k t + z x+y=kt+z x+y=kt+z,令 x + y − z = k , t = 1 x+y-z=k,t=1 x+yz=k,t=1,并且要满足 k > z & k > x k>z\&k>x k>z&k>x

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        int x,y,z,k=-1;
        cin >>x>>y>>z;
        if(x+y==z)k=max(z+1,x+1);
        else if(x+y>z&&x+y-z>z&&x+y-z>x)k=x+y-z;
        cout <<k<<endl;
    }
    return 0;
}

H

题目大意:给出1-n共n个数,对n个数进行分组,组内元素的最大公因数不能是1,求最多能构成多少组,并输出方案

思路:存下每个质因数的倍数,一个质因数的倍数可以互相组队,组队的时候按照质因数从大到小的顺序,如果质因数的倍数(包括自己)的个数是偶数(去重后),则可以组完,如果是奇数,把其二倍跳过,剩下的一定能组完,最后所有2倍都被分给了2

PS:感谢大师兄,宝刀不老

代码

#include <bits/stdc++.h>
using namespace std;
int T,n;
bool IsPrime[121212];//真值为素数
bool vis[121212];
int Prime[121212],ans;
vector<int> res[121212];
void Choose(int n) { //筛选到n
    memset(IsPrime,1,sizeof(IsPrime));//初始化
    //假设每个数为素数
    IsPrime[1]=IsPrime[0]=0;
    for(int i=2; i<=n; i++) {
        if(IsPrime[i])//如果这个数没筛掉,那么将其加入素数序列
            Prime[++ans]=i;
        for(int j=1; j<=ans&&i*Prime[j]<=n; j++) {
            IsPrime[i*Prime[j]]=0;
            if(!i%Prime[j])break;
        }
    }
}
int main() {
    Choose(121212);
    cin >>T;
    while(T--) {
        int num=0;
        cin >>n;
        for(int i=n; i>=2; i--)
            if(!IsPrime[i])continue;
            else {//遇到质数
                int cnt=1,s=0;//计数自己
                vis[i]=1;//标记自身
                for(int j=2*i; j<=n; j+=i)//统计倍数
                    if(!vis[j])cnt++;
                cnt&1?s=3*i:s=2*i;//包括自己在内的数
                res[i].push_back(i);//录入自己
                if(cnt>1)num++;//记录所有使用的数
                for(int j=s; j<=n; j+=i)//
                    if(!vis[j]) {
                        res[i].push_back(j);
                        vis[j]=1;
                        num++;
                    }
            }
        cout <<num/2;//两个一组
        for(int i=2; i<=n; i++)
            if(res[i].size()>1) {
                int len=res[i].size();
                for(int j=0; j<len; j+=2)
                    cout <<" "<<res[i][j]<<" "<<res[i][j+1];
            }
        cout <<endl;
        memset(vis,0,sizeof(vis));
        for(int i=2; i<=n; i++)res[i].clear();
    }
    return 0;
}

I

题目大意:给出一个无根字典树,每条边标记着一个字母,求出有多少个节点可以作为根节点使得其到每个节点构成的字符串不同

思路:如果有一个点连了多对相同边或者多条相同边,肯定无解,对于整棵树DFS获得DFS序,对于连了边权相同的点 u u u,假设两条边对应的顶点为 t 1 , t 2 ( d f n ( t 2 ) > d f n ( t 1 ) ) t_1,t_2(dfn(t2)>dfn(t1)) t1,t2(dfn(t2)>dfn(t1)),分情况讨论

  1. d f n ( t 2 ) > d f n ( t 1 ) , d f n ( u ) > d f n ( t 1 ) dfn(t2)>dfn(t1),dfn(u)>dfn(t1) dfn(t2)>dfn(t1),dfn(u)>dfn(t1)
    u u u的DFS序在两者中间,那么可以选取的根节点区间为除去 u u u的所有区间: [ 1 , d f n ( u ) − 1 ] ∪ [ d f n ( u ) + 1 , n ] ∪ [ d f n ( t 2 ) , d f o ( t 2 ) ] [1,dfn(u)-1]∪[dfn(u)+1,n]∪[dfn(t2),dfo(t2)] [1,dfn(u)1][dfn(u)+1,n][dfn(t2),dfo(t2)]
  2. d f n ( u ) < d f n ( t 1 ) dfn(u)<dfn(t1) dfn(u)<dfn(t1)
    u u u的DFS序小于两者, u u u为根节点,可行的解区间为 [ d f n ( t 1 ) , d f o ( t 1 ) ] ∪ [ d f n ( t 2 ) , d f o ( t 2 ) ] [dfn(t1),dfo(t1)]∪[dfn(t2),dfo(t2)] [dfn(t1),dfo(t1)][dfn(t2),dfo(t2)]

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
int head[maxn],cnt,dfn[maxn],dfo[maxn],ans,T,n;
int l1[maxn],r1[maxn],l2[maxn],r2[maxn],l3[maxn],r3[maxn],res[maxn];
bool vis[maxn];
struct node {
    int next,to,val;
} e[maxn*3];
void Add(int from,int to,char val) {
    e[++cnt].next=head[from];
    e[cnt].to=to;
    e[cnt].val=val-'a';
    head[from]=cnt;
}
void DFS(int u) {//DFS获得DFS序
    vis[u]=1;
    dfn[u]=++ans;
    for(int i=head[u]; i; i=e[i].next) {
        int v=e[i].to;
        if(!vis[v])DFS(v);
    }
    dfo[u]=ans;
}
void segment(int u) {
    vector<int>alpha[30];
    for(int i=head[u]; i; i=e[i].next) {//获取与u边权相同的两个点
        int v=e[i].to;
        alpha[e[i].val].push_back(v);
    }
    for(int i=0; i<26; i++) {
        if(alpha[i].size()<2)continue;
        int t1=alpha[i][0],t2=alpha[i][1];//获得两点编号
        if(dfn[t1]>dfn[t2])swap(t1,t2);
        if(dfn[u]>dfn[t1]) {//判断DFS序相对大小关系,t2>t1,u>t1
            l1[u]=1;
            r1[u]=dfn[u]-1;//除去u之后dfs序在u之前的点
            l2[u]=dfn[t2];
            r2[u]=dfo[t2];//t2的子树
            l3[u]=dfo[u]+1;
            r3[u]=n;//除去u之后dfs序在u之后的点
        } else {//t2>t1>u,u是根,范围为两棵树的子树(包括自己)
            l1[u]=dfn[t1];
            l2[u]=dfn[t2];
            r1[u]=dfo[t1];
            r2[u]=dfo[t2];
        }
        break;
    }
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>T;
    while(T--) {
        memset(res,0,sizeof(res));
        memset(l1,0,sizeof(l1));
        memset(l3,0,sizeof(l3));
        memset(vis,0,sizeof(vis));
        memset(head,0,sizeof(head));
        cnt=ans=0;
        cin >>n;
        int u,v,root=0,un=0;
        char ch;
        bool flag=0;
        for(int i=1; i<=n-1; i++) {//建树
            cin >>u>>v>>ch;
            Add(v,u,ch);
            Add(u,v,ch);
        }
        for(int i=1; i<=n; i++) {//特判
            int alpha[30]= {0},sum=0,u=i;
            for(int i=head[u]; i; i=e[i].next)
                alpha[e[i].val]++;
            for(int i=0; i<26; i++)
                if(alpha[i]>2)flag=1;//如果三条及以上边相同
                else if(alpha[i]==2)sum++;
            if(sum>1||flag) {//如果一个点连至少两对相同的边
                flag=1;
                break;
            }
        }
        if(flag) {
            cout <<"0\n";
            continue;
        }
        DFS(1);//构造DFS序
        for(int i=1; i<=n; i++) {
            segment(i);//获得可行解区间
            if(!l1[i]) { //判断区间是否存在,不存在代表该点可选并且对其他节点的选取无区间限制
                un++;
                continue;
            }
            res[l1[i]]++;//差分获得可做根节点的区间
            res[r1[i]+1]--;
            res[l2[i]]++;
            res[r2[i]+1]--;
            if(!l3[i])continue;
            res[l3[i]]++;
            res[r3[i]+1]--;
        }
        int sum=0;
        for(int i=1; i<=n; i++) {
            sum+=res[i];
            if(sum==n-un)root++;//如果一个点被覆盖的次数正好是整体点-任意点
            //任意点没有对该点进行区间限制,所以需要去掉其个数
        }
        cout <<root<<endl;
    }
    return 0;
}

L

题目大意:定义一个函数 f ( x ) f(x) f(x)计算 x x x的各位上数的乘积,对于给定的范围 [ l , r ] [l,r] [l,r],求 ( ∏ i = l r f ( i ) ) % ( 1 e 9 + 7 ) (\prod_{i=l}^rf(i))\%(1e9+7) (i=lrf(i))%(1e9+7)

思路:可以很容易地发现,当 [ l , r ] [l,r] [l,r]的区间长度超过10时,一定存在一个数的 f ( x ) f(x) f(x)是0,导致整个式子结果为0,如果 l l l的个位大于 r r r的个位,代表一定通过了一个个位是0的数,结果同理,那么就很好求解了

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
int t;
ll f(ll x) {//计算f(x)
    ll a=1;
    while(x) {
        a*=x%10;
        x/=10;
    }
    return a;
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        ll l,r;
        ll res=1;
        cin >>l>>r;
        if(l==r)res=f(l);//特判相等
        else if(r-l+1>=10||l%10>=r%10)res=0;//如果越过了0
        else
            for(ll i=l; i<=r; i++)res=res*f(i)%mod;//直接算即可,最多81个数相乘
        cout <<res<<endl;
    }
    return 0;
}

参考文献

  1. ACM2019陕西省赛-ZOJ4128-0689(思维)
  2. ZOJ4134 Unrooted Trie(dfs序+线段树)
  3. ZOJ4134 Unrooted Trie(树上DFS序列+前缀和)
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值