平时二十二测

第一题水题未放,今天第二题又读入超时2000*2000的读入要快读啊,上次没长教训

第二题:

图论题.二维前缀和的应用. 

最重要的:

这就是一棵树啊

标算为:对于不包含环的图,连通块数目=点数-边数,所以利用二维前缀和进行预处理,O(1)求出矩形区域内的边数和点数.

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2005;
int sum[maxn][maxn], row[maxn][maxn], line[maxn][maxn], a[maxn][maxn];
int read(){
    int x = 0; int f = 1; char c = getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
    return x*=f;
}
char s[2005];

int main(){
    freopen("duty.in","r",stdin);
    freopen("duty.out","w",stdout);
    int N = read(), M = read(), Q = read();
    for(int i = 1; i <= N; i++){
        scanf("%s", s);
        
        for(int j = 1; j <= M; j++){
            a[i][j] = s[j - 1] -'0';
            sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
        }
    }
        
    for(int i = 1; i <= N; i++)
        for(int j = 1; j <= M; j++) {
            if(a[i][j] && a[i+1][j]) line[i][j]++;
            line[i][j] += line[i][j - 1];
        }
    for(int i = 1; i <= N; i++)//?
        for(int j = 1; j <= M; j++) line[i][j] += line[i - 1][j];
    
    for(int j = 1; j <= M; j++)
        for(int i = 1; i <= N; i++) {
            if(a[i][j] && a[i][j+1]) row[i][j]++;
            row[i][j] += row[i - 1][j];
        }
    for(int j = 1; j <= M; j++)
        for(int i = 1; i <= N; i++) row[i][j] += row[i][j - 1];    
    while(Q--){
        int xl = read(), yl = read(), xr = read(), yr = read();
        int point = sum[xr][yr] - sum[xr][yl-1] - sum[xl-1][yr] + sum[xl-1][yl-1];
        int edge1 = line[xr-1][yr] - line[xr-1][yl-1] - line[xl-1][yr] + line[xl-1][yl-1];
        int edge2 = row[xr][yr-1] - row[xr][yl-1] - row[xl-1][yr-1] + row[xl-1][yl-1];
        //printf("%d %d %d\n", edge1, edge2, point);
        printf("%d\n", point - edge1 - edge2); 
    }
    
}
View Code

第三题:

推性质:答案就是x[1],x[2]…x[n]这个序列的逆序对个数.

采用经典的树状数组或分治法在O(nlogn)的时间内求出逆序对数目即可.

可以得到n<=100000时的40分.

x[1]=a的数据告诉我们,x[i]=i*a%mod

假设x[i]=x[i-1]+a(也就是x[i-1]+a<mod),且x[i-1]和前面所有数字形成了m个逆序对,同时,除去x[i-1]和x[i]所在的等差数列,x[i]前面的所有数字可以分成k段等差序列,那么x[i]将和前面所有数字组成m-k个逆序对.

原因在于:每段等差序列中必然有一个数字和x[i-1]能组成逆序对,但不能和x[i]组成逆序对.那么每段等差数列的贡献都会减1.

因此我们可以O(1)从x[i-1]的贡献得到x[i]的贡献.

如果x[i]<a,不存在对应的x[i-1],我们需要直接计算它的贡献.前面有i-1个数字,我们数出有多少个数字不产生贡献(即小于x[i]的数字个数),即可求出有多少个数字形成了逆序对.用树状数组维护小于a的所有数值,可以在O(loga)的时间内完成一次这样的计算.小于a的数字至多有a个,所以这一部分的时间复杂度为O(aloga),空间复杂度为O(a)

总的时间复杂度为O(aloga+n)

可以得到x[1]=a的20分.

算法7:

算法6几乎就是满分做法了.现在x[1]!=a,我们只需针对最开始的一段不完整等差数列加一些特判就可以通过本题.

可以得到100分.

算法8:

实际上存在更加优越的算法,复杂度为O(aloga),不需要带有一个O(n)

考虑算法6,7中我们都把整个序列划分为了O(a)段.实际上同一段中所有元素的贡献是一个等差数列的形式,可以直接求和.细节可能较多.

#include<bits/stdc++.h>
using namespace std;
const int M = 1e5 + 5;
int c[M], a;

int query(int x){
    int ret = 0;
    x++;
    for( ; x; x -= x&(-x)) ret += c[x];
    return ret;
}
void add(int x){
    x++;
    for(; x <= a; x += x&(-x)) c[x]++;
}

int main(){
    freopen("fly.in","r",stdin);
    freopen("fly.out","w",stdout);
    int n, mod, x1;
    long long ans=0;
    scanf("%d%d%d%d",&n,&x1,&a,&mod);
    int x = x1;
    int del = 0, cir = 0;
    for(int i = 1; i <= n; i++){
        if(x >= a){
            del -= cir;
            if(x < x1) del++;
            ans += del;
        }
        else {
            del = (i-1) - query(x);
            ans += del;
            add(x);
        }
        x += a;
        if(x >= mod){
            cir++; x -= mod;
        }
    }
    printf("%lld\n",ans);
} 
View Code

今天题难度还不错,但一天考两次我真的受不了了

猜你喜欢

转载自www.cnblogs.com/EdSheeran/p/9897518.html