其他分享
首页 > 其他分享> > CSUST--4.11排位周赛第八场 (全解)

CSUST--4.11排位周赛第八场 (全解)

作者:互联网

emmm,怎么基本都爆零了啊,B题是个签到题啊。。。诶,你们这次应该感觉到了社会的毒打了,是不是很难受,是不是很绝望QWQ

题目说明:

A.这真是一个水题吗(Hash+二分)

B.动漫明星大乱斗(签到题-思维)

C.欺负萌新的佳爷(二分)

D.小明的数学作业(DP+思维)

E.还没想好题目的题(最短路)

比赛链接:http://acm.csust.edu.cn/contest/88

比赛过后无法提交,请到problem中提交

 

A.这真是一个水题吗

题目大意:给你一个字符串$s_{1}$和字符串$s_{2}$,$s_{2}$作为匹配串,问允许最大不匹配数为k时的最多匹配位置有多少($s_{1},s_{2}<=1e6$)

Sample Input  

adada
add
1

Sample Output 

2

emmm,看起来暴力好像能过,然后白给了一发,我想多了,假设$s_{1}$长度为1e6,$s_{2}$的长度为1e3。。。然后极限复杂度就是1e9,爆了

不过我们可以先看看暴力的代码:

#include <bits/stdc++.h>
using namespace std;

const int mac=1e6+10;

char s1[mac],s2[mac];

int main()
{
    int k;
    scanf ("%s%s",s1,s2);
    scanf ("%d",&k);
    int ans=0;
    int len1=strlen(s1),len2=strlen(s2);
    for (int i=0; i<len1; i++){
        if (i+len2-1>=len1) break;
        int dif=0;
        for (int j=0; j<len2; j++){
            if (s1[i+j]!=s2[j]) dif++;
            if (dif>k) break;
        }
        if (dif<=k) ans++;
    }
    printf("%d\n",ans);
    return 0;
}
View Code

我们先将这两个串hash化,对于有多少个不匹配点,我们可以直接从1到p进行枚举,每枚举一次就对初始匹配点跳跃一次,在枚举的时候我们二分在当前初始匹配点的时候最远能匹配的长度。。。。然后似乎就没了QAQ

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef unsigned long long ull;
const int mac=1e6+10;
const ull mod=53;

char s1[mac],s2[mac];
ull hash1[mac],hash2[mac],miss[mac];
int p;


void get_Hash(char *s1,char *s2)
{
    int len1=strlen(s1+1),len2=strlen(s2+1);
    for (int i=1; i<=len1; i++) hash1[i]=hash1[i-1]*mod+(s1[i]-'a'+1);
    for (int i=1; i<=len2; i++) hash2[i]=hash2[i-1]*mod+(s2[i]-'a'+1);
}

int check(int x,int y,int mid)
{
    ull t1=hash1[x+mid-1]-hash1[x-1]*miss[mid];
    ull t2=hash2[y+mid-1]-hash2[y-1]*miss[mid];
    if (t1==t2) return 1;
    return 0;
}

int find(int x,int y,int len2)
{
    int l=1,r=len2-y+1;
    int ans=0;
    while (l<=r){
        int mid=(l+r)>>1;
        if (check(x,y,mid)) {
            ans=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    return ans;
}

int ok(int pos,int len2)
{
    int x=pos,y=1;
    for (int i=1; i<=p; i++){
        int use=find(x,y,len2);
        x=x+use+1,y=y+use+1;
        if (y>len2) return 1;
    }
    int use=find(x,y,len2);
    y+=use;
    if (y>len2) return 1;
    return 0;
}

int main()
{
    scanf ("%s%s%d",s1+1,s2+1,&p);
    miss[0]=1;
    for (int i=1; i<mac; i++)
        miss[i]=miss[i-1]*mod;
    get_Hash(s1,s2);
    int len1=strlen(s1+1),len2=strlen(s2+1);
    int ans=0; 
    for (int i=1; i<=len1-len2+1; i++)
        if (ok(i,len2)) ans++;
    printf ("%d\n",ans);
    return 0;
}
View Code

 

B.动漫明星大乱斗

题目大意:给你第1-n个人要挑战的人,这n个人当中有一个发起挑战的人,自挑战发起开始每次被选的人都会选一个人与之对决,并不停的进行下去。问无论发起者是谁,都最多打一场的人有多少,并输出他们的编号(从小到大)

Sample Input 

2
2 1

Sample Output 

0

实际上我们画个图就出来了,这是个有向图,当最多只有一场的时候那么他们的度只能小于等于1比如:

 B的度为2,那么答案只能是A和C,所以这题就很简单了,记录一下每个点的出度和入度就完事了

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

const int mac=1e5+10;

int a[mac],du_in[mac],du_out[mac];
int id[mac];

int main()
{
    int n;
    scanf ("%d",&n);
    for (int i=1; i<=n; i++){
        scanf ("%d",&a[i]);
        du_out[i]++;
        du_in[a[i]]++;
    }
    int cnt=0;
    for (int i=1; i<=n; i++)
        if (du_in[i]+du_out[i]<=1) id[++cnt]=i;
    sort(id+1,id+1+cnt);
    printf("%d\n",cnt );
    for (int i=1; i<=cnt; i++)
        printf("%d ",id[i]);
    printf("\n");
    return 0;
}
View Code

 

C.欺负萌新的佳爷

题目大意:给你一个n*m的矩阵,矩阵中第$x$行,第$y$列数字的大小为$ax^2+bx+cy$,问你第$k$大的数字是多少。给出$n,m,k(n,m<=1e5),a,b,c([-1e5,1e5])$

Sample Input 

3 3 4
1 1 1

Sample Output 

9

样例构成的矩阵为:

3     4     5

7     8     9

13   14  15

刚开始看的时候可能有点儿蒙,但其实也不是很难,我们观察他所给定的式子就会发现如果固定下$x$那么该行的值是单调的,也就是说我们可以快速地求出每一行的第$k$大,这有什么用呢?事实上,整体的第$k$大也就是代表着矩阵中有$k-1$个数比他大,那么我们枚举答案$ans$,看看是否有$k-1$个数比他大,但我们得到的$ans$是一个区间即[第k大,第k+1大),那么我们只需要取最小值就OK了。而枚举答案实际上就是二分答案,最后的总复杂度就是$O(nlogval)$(val<=2e18)

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const ll inf=1e18;
ll n,m,a,b,c,k;

ll ok(ll x)
{//计算有多少个大于x的数
    ll num=0;
    if (c==0){
        for (int i=1; i<=n; i++){
            ll p=a*i*i+b*i;
            if (p>x) num+=m;
        }
    }
    else if (c<0){//每行递减
        for (int i=1; i<=n; i++){
            ll p=a*i*i+b*i;
            ll y=(x-p)/c;//计算x所处的位置
            if (y<=0) continue;
            else if (y>m) num+=m;
            else if (abs(x-p)%abs(c)) num+=y;
            else num+=y-1;
        }
    }
    else {
        for (int i=1; i<=n; i++){
            ll p=a*i*i+b*i;
            ll y=(x-p)/c;
            if (y<=0) num+=m;
            else if (y>m) continue;
            else if ((x-p)%c) num+=m-y;
            else num+=m-y;
        }
    }
    return num;
}

int main()
{
    cin>>n>>m>>k;
    cin>>a>>b>>c;
    ll l=inf,r=-inf,mid,ans=0;
    for (int i=1; i<=n; i++){
        if (c>0){
            l=min(l,a*i*i+b*i+c);
            r=max(r,a*i*i+b*i+c*m);
        }
        else {
            l=min(l,a*i*i+b*i+c*m);
            r=max(r,a*i*i+b*i+c);
        }
    }
    while (l<=r){
        mid=(l+r)>>1;
        ll nb=ok(mid);
        if (nb<k){
            ans=mid;
            r=mid-1;
        }
        else {
            l=mid+1;
        }
    }
    cout<<ans<<endl;
    return 0;
}
View Code

 

D.小明的数学作业

题目大意:给你一个数列,让你用里面的数字组成一个最长的等差数列,问这个最长的等差数列多长?

Sample Input 

6
0 1 3 5 6 9

Sample Output

4

emmmm,首先我们能够想到的是如果等差数列的其中两项被固定了,那么这个等差数列就是固定的,所以我们可以枚举等差数列的其中两项来得到一个公差,并通过当前第二项的$a_i+d=val$判断val是否存在,并且存在哪里,我们就可以通过这个地方来进行转移,即:$dp[i][j]=dp[j][a[j]+d]+1$当然,我们的$a[j]+d$肯定是用map来映射的一个该值的位置。我们还要进行一下小优化,如果公差太大的话就直接跳出循环:$if (1LL*d*ans>a[n]-a[1]) break;$

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

const int mac=5e3+10;

short dp[mac][mac];
int a[mac];

int main() {
    int n;
    scanf("%d", &n);
    for (int i=1; i<=n; i++)
        scanf ("%d",&a[i]);
    sort(a+1,a+1+n);
    unordered_map<int,short>q,q1;
    short ans=2;
    for (int i=1; i<=n; i++) q[a[i]]=i,q1[a[i]]++,ans=max(ans,q1[a[i]]);//记录a[i]出现的位置
    for (int j=n; j>=2; j--){//枚举两项
        for (int i=j-1; i>=1; i--){
            int d=a[j]-a[i];
            if (1LL*d*ans>a[n]-a[1]) break;//公差太大了
            int last=a[j]+d;
            if (!q.count(last)) dp[i][j]=2;
            else dp[i][j]=dp[j][q[last]]+1;
            ans=max(ans,dp[i][j]);
        }
    }
    printf("%d\n", (int)ans);
}
View Code

 

F.还没想好题目的题

题目大意:给你一个图让你求$1\rightarrow 2\rightarrow 3\rightarrow \cdots \rightarrow n$的距离

Sample Input 

1
3 2
1 2 1
2 3 2

Sample Output 

3

题目的数据范围不是很大,n是1000,m是10000,所以我们可以直接跑n遍dij最短路就完事儿了。。。然后你会发现T了。。。dij是算边跑的,它的理论复杂度应该是$O(mlogm)$。。。所以我们要压缩一下时间,其实我们只需要当dij将点扩展到st+1的时候就完事了,后面的点不用管它。这样就大大节约了时间

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

const int mac=1e4+10;
typedef long long ll;
const ll inf=1e18+10;

ll dis[mac];
struct node
{
    int to,next,w;
}eg[mac<<1];
int head[mac],num=0;
bool vis[mac];
struct pt
{
    int id;
    ll s;
    bool operator<(const pt&a)const{
        return s>a.s;
    }
};

void dij(int st,int n)
{
    for (int i=1; i<=n; i++) dis[i]=inf;
    memset(vis,false,sizeof vis);
    dis[st]=0;
    priority_queue<pt>q;
    q.push(pt{st,0});
    while (!q.empty()){
        pt now=q.top();
        q.pop();
        int u=now.id;
        if (u==st+1) break;
        if (vis[u]) continue;
        vis[u]=true;
        for (int i=head[u]; i!=-1; i=eg[i].next){
            int v=eg[i].to;
            if (vis[v]) continue;
            if (dis[u]+eg[i].w<dis[v]){
                dis[v]=dis[u]+eg[i].w;
                q.push(pt{v,dis[v]});
            }
        }
    }
}

void add(int u,int v,int w)
{
    eg[++num]=node{v,head[u],w};
    head[u]=num;
}

int main()
{
    int t,n,m;
    scanf ("%d",&t);
    while (t--){
        scanf ("%d%d",&n,&m);
        memset(head,-1,sizeof head);
        num=0;
        for (int i=1; i<=m; i++){
            int u,v,w;
            scanf ("%d%d%d",&u,&v,&w);
            add(u,v,w);add(v,u,w);
        }
        ll ans=0;
        for (int i=1; i<n; i++){
            dij(i,n);
            ans+=dis[i+1];
        }
        printf("%lld\n",ans);
    }
    return 0;
}
View Code

 

标签:4.11,周赛,int,ll,CSUST,else,Sample,mac,ans
来源: https://www.cnblogs.com/lonely-wind-/p/12690805.html