暑假2019培训:Day9提高组测试赛

暑假2019培训:Day9提高组测试赛

  • 概述
  • 题目顺序
  • 黑白反转(black)
    • 【题目描述】
        • 【输入格式】
        • 【输出格式】
          • 【样例输入 1】(ex_black1.in)
          • 【样例输出 1】(ex_black1.ans)
        • 【数据范围】
  • 茉莉花茶(jasmine)
    • 【问题描述】
        • 【输入格式】
        • 【输出格式】
          • 【样例输入 1】
          • 【样例输出 1】
            • 【样例解释 1】
          • 【样例输入 2】
          • 【样例输出 2】
            • 【样例解释 2】
          • 【样例输入 3】
          • 【样例输出 3】
            • 【样例解释 3】
        • 【数据规模和约定】
  • 飞(fly)
    • 【题目描述】
        • 【输入格式】
        • 【输出格式】
          • 【样例输入 1】(即 ex_fly1.in)
          • 【样例输出 1】(即 ex_fly1.ans)
        • 【数据范围】
  • 结束~~

概述

今天是来自清华的刘润达给我们讲课~
他有着一个惊人的生世~:
他来自衡水一中(就是那个严死的学校,虽然一本线率将近100%,当我想及时我能进也是不会去的(虽然我应该进不去))
他信息学noip提高组2016,他Day拿了300分,没错A了天天爱跑步和蚯蚓,Day2拿了285分,没有错他A了愤怒的小鸟~
但可惜他有一次信息学考试第二天发烧,而退役~
刘润达回到衡水一中,当头来了场月考,几个月没有学文化课的他,考了全校七八十名(注意是衡水)
真是恐怖,更恐怖的是,他学了一个月,就拿到了全校第一,且保持着,最终进入清华~
他有多猛啊~
真的猛,猛,猛~
上课也猛,也很风趣~
顺带提一下,想不到他也喜欢《福5鼠》这部高级电视剧,可惜我喜欢小气猫,他喜欢福福鼠~(可能这就是我和大佬的区别吧)
在这里插入图片描述


题目顺序

  • 1.黑白反转(black)
  • 2.茉莉花茶(jasmine)
  • 3.飞(fly)

黑白反转(black)

【题目描述】

这是一道描述很直接的题目.
给出一个 n*n 的二维矩阵, 初始全为白色(用 0 表示).有以下 3 种操作:

  1. hang L R 表示把第 L 行到第 R 行所有元素黑白反转(0 变成 1,1 变成 0)
  2. lie L R 表示把第 L 列到第 R 列所有元素黑白反转(0 变成 1,1 变成 0)
  3. chaxun x1 x2 y1 y2 查询第 x1 行到第 x2 行,第 y1 列到第 y2 列组成的矩形区域内
    有多少个格子为黑色(也就是有多少格子数值为 1)
    你所要做的就是依次处理这些操作,并对所有的第三类操作,输出查询的结果

【输入格式】

第一行两个整数 n,m,表示矩阵的大小为 n,共 m 次操作.
接下来 m 行,每行表示一个操作,每个操作都由一个字符串开头.
如果开头的字符串是 hang,后面两个整数 L,R 表示反转的行的范围.
如果开头的字符串是 lie,后面两个整数 L,R 表示反转的列的范围.
如果开头的字符串是 chaxun,后面四个整数 x1 x2 y1 y2 表示查询的行的范围是从 x1
到 x2, 列的范围是从 y1 到 y2.

【输出格式】

对每个 chaxun 操作,输出一行一个整数 ans 表示[x1,x2]行,[y1,y2]列这个矩形区域内
黑色格子的个数.

【样例输入 1】(ex_black1.in)

10 10
lie 4 7
lie 5 6
hang 1 1
chaxun 1 5 1 2
hang 3 4
lie 1 8
chaxun 7 7 2 7
chaxun 2 4 2 5
chaxun 3 6 1 6
hang 1 6

【样例输出 1】(ex_black1.ans)

2
4
5
12

【数据范围】

第 1,2 个测试点,n<=200,m<=200
第 3,4 个测试点,没有第 1 类操作”hang”
第 5,6,7 个测试点,所有第 3 类操作”chaxun”满足 x1=x2,y1=y2
全部测试点,n<=200000,m<=200000,1<=x1<=x2<=n,1<=y1<=y2<=n,1<=L<=R<=n

========================================================
20分好拿的要死,暴力搜索,直接上代码~

#include
using namespace std;
const int N=20000+5;
string s,s1;
int n,m,k;
bool vis[N][N]={};
inline int ask_sum(int hx,int hy,int lx,int ly)
{
	int ans=0;
	for(int i=hx-1;++i<=hy;)
	   for(int j=lx-1;++j<=ly;)
	      ans+=(int)vis[i][j];
	return ans;
}
inline void add_hang(int x,int y)
{
	for(int i=x-1;++i<=y;)
	    for(int j=0;++j<=n;)
	        vis[i][j]=(!vis[i][j]);
/*	cout<<"----------------------\n";
	for(int i=0;++i<=n;puts(""))
	  for(int j=0;++j<=n;)
	    cout<
}
inline void add_lie(int x,int y)
{
	for(int i=0;++i<=n;)
	    for(int j=x-1;++j<=y;)
	        vis[i][j]=(!vis[i][j]);
/*	cout<<"----------------------\n";
	for(int i=0;++i<=n;puts(""))
	  for(int j=0;++j<=n;)
	    cout<
}
int main()
{
	freopen("black.in","r",stdin);
	freopen("black.out","w",stdout);
	int i;
	for(memset(vis,0,sizeof(vis)),scanf("%d%d",&n,&m),i=0;++i<=m;)
	{
		cin>>s;
		if(s[0]=='l')
		{
			int x,y;
		    scanf("%d%d",&x,&y);
			if(x>y)swap(x,y);
		    add_lie(x,y);
	    }
	    else
	    if(s[0]=='h')
	    {
	    	int x,y;
		    scanf("%d%d",&x,&y);
			if(x>y)swap(x,y);
		    add_hang(x,y);
		}
		else
		{
			int hx,hy,lx,ly;
			scanf("%d%d%d%d",&hx,&hy,&lx,&ly);
			printf("%d\n",ask_sum(hx,hy,lx,ly));
		}
	}
	return 0;
}

100分:
这是一道送分 (送命)的数据结构题目.
如何知道第i行j列当前的颜色?只需知道第i行被反转了多少次,第j行被反转了多少次,之后考虑反转总次数的奇偶性即可.
如何知道一个矩形区域内有多少个格子是黑色? 我们就需要统计这样的(i,j)有多少对: 首先x1<=i<=x2,y1<=j<=y2(保证格子在矩形区域内),并且第i行和第j列被反转的次数加起来是奇数.
实际上,我们只需要统计x1到x2有多少行被反转偶数次/奇数次,y1到y2有多少列被反转偶数次/奇数次, 这个区域内黑色格子的个数就是
反转偶数次的行数 * 反转奇数次的列数+反转奇数次的行数 * 反转偶数次的列数
显然行列可以分开统计的,而且写法基本相同, 用线段树维护区间反转并进行求和即可.
借助一点struct的使用,出题人(强啊)自然而然地用不到70行写完了这个老掉牙的套路题.

那刘润达的代码就是~

#include
void read(int &typ,int &l1,int &r1,int &l2, int &r2){
    static char str[10];
    scanf("%s",str);
    if(str[0]=='h'){
        typ=1;scanf("%d%d",&l1,&r1);
    }else if(str[0]=='l'){
        typ=2;scanf("%d%d",&l2,&r2);
    }else{
        typ=3;scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
    }
}
const int maxn=200005;
struct seg{
    int t[maxn<<2],mk[maxn<<2];
    int sum(int,int,int,const int&,const int&);
    void flip(int,int,int,const int&,const int&);
    inline void pushdown(int,int,int);    
    inline void flip(int,int,int);
}tx,ty;
inline void seg::flip(int rt,int l,int r){
    mk[rt]^=1;
    t[rt]=(r-l+1-t[rt]);
}
inline void seg::pushdown(int rt,int l,int r){
    if(mk[rt]){
        mk[rt]=0;
        int mid=(l+r)>>1;
        flip(rt<<1,l,mid);flip(rt<<1|1,mid+1,r);
    }
}
void seg::flip(int rt,int l,int r,const int &ql,const int &qr){
    if(ql<=l&&r<=qr)flip(rt,l,r);
    else{
        pushdown(rt,l,r);
        int mid=(l+r)>>1;
        if(ql<=mid)flip(rt<<1,l,mid,ql,qr);
        if(qr>mid)flip(rt<<1|1,mid+1,r,ql,qr);
        t[rt]=t[rt<<1]+t[rt<<1|1];
    }
}
int seg::sum(int rt,int l,int r,const int &ql,const int &qr){
    if(ql<=l&&r<=qr)return t[rt];
    pushdown(rt,l,r);
    int ans=0,mid=(l+r)>>1;
    if(ql<=mid)ans+=sum(rt<<1,l,mid,ql,qr);
    if(qr>mid)ans+=sum(rt<<1|1,mid+1,r,ql,qr);
    return ans;
}
int main(){
    freopen("black.in","r",stdin);
    freopen("black.out","w",stdout);
    int n,m;scanf("%d%d",&n,&m);
    int t,x1,x2,y1,y2;
    while(m--){
        read(t,x1,x2,y1,y2);    
        if(t==1){
            tx.flip(1,1,n,x1,x2);
        }else if(t==2){
            ty.flip(1,1,n,y1,y2);
        }else{
            int ansx1=tx.sum(1,1,n,x1,x2),ansy1=ty.sum(1,1,n,y1,y2);
            int sumx=x2-x1+1,sumy=y2-y1+1;
            long long ans01=(sumx-ansx1)*1ll*ansy1,ans10=ansx1*1ll*(sumy-ansy1);
            printf("%lld\n",ans01+ans10);
        }
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

茉莉花茶(jasmine)

【问题描述】

Jasmine 喜欢喝茉莉花茶.
Jasmine 家里有一棵以 1 为根节点的树,树上的每个节点上有一种茉莉花茶.
这些茉莉花茶的品种互不相同.如果一个点 u 是某个点 v(u!=v)的祖先(也就是说,u
位于 1 到 v 的路径上),那么我们就说 v 是 u 的”下属品种”.规定一个节点 v 也是节
点 v 本身的”下属品种”
Jasmine 很想给自己续命.每天,Jasmine 都会喝两杯茉莉花茶.这两杯茉莉花茶
的种类可以一样,也可以不一样.但是这两杯茶是有顺序的.也就是说,如果这两杯
茶的种类不一样,那么这两杯茶喝下去的先后顺序不同时,会得到两种不同的喝法.
Jasmine 认为,每有一种不同的喝法,他就能给自己续命 1 秒.
也就是说,如果这棵树本来有 n 个节点,Jasmine 可以给自己续 n2 秒.
Jasmine 认为有些茶一起喝的时候会出现偏差.
Jasmine 规定了 m 个”冲突”,每个”冲突”包含两个节点 u,v,如果一种喝法的两
杯茶分别是 u 和 v 的”下属品种”,那么这种喝法被禁止.u 可能 等于 v,这个时候表
示不能两杯茶都来自这一个节点的”下属品种”
Jasmine 想知道他能给自己续多少秒.也就是说,有多少种没被禁止的喝法.

【输入格式】

第一行两个整数 n,m,之间用空格隔开,表示树的节点数 n 和冲突数 m.
接下来一行 n-1 整数,之间用空格隔开,第 i 个数字表示第 i+1 个节点的父亲
节点的编号.
接下来 m 行, 表示 m 对冲突.每行一对整数 u,v, 表示一种喝法的两杯茶不能
分别是 u 和 v 的”下属品种”.

【输出格式】

一行一个整数表示 Jasmine 能给自己续多少秒

【样例输入 1】

3 0
1 2

【样例输出 1】

9

【样例解释 1】

一条长度为 3 的链,没有冲突,所以有全部 3*3=9 种喝法.

【样例输入 2】

3 1
1 2
3 3

【样例输出 2】

8

【样例解释 2】

只有一个”冲突”,且只影响了(3,3)这种喝法

【样例输入 3】

3 2
1 2
2 3
3 3

【样例输出 3】

6

【样例解释 3】

有两个”冲突”,影响了(3,3)(2,3)(3,2)这 3 种喝法
另外三个较大的样例输入输出文件见下发的 ex_jasmine1.in~ex_jasmine3.in以及
ex_jasmine1.ans~ex_jasmine3.ans

【数据规模和约定】

对所有测试点,1<=n<=200000,0<=m<=100000,给出的节点父亲能够形成一棵
以 1 为根的树.m 个”冲突”中的节点编号都是 1 到 n 之间的整数 .
第 1 个测试点,满足 m=0
第 2,3 个测试点,满足 m=1
第 4,5 个测试点,所有”冲突”都满足 u=v
第 6,7 个测试点,满足第 i 个节点的父亲是第 i-1 个节点.
第 8,9,10 个测试点,无特殊限制.

========================================================

注意使用long long.否则你最多能取得??分的好成绩.
第1个测试点:送分,输出n2.
第2,3个测试点:送分.处理一个冲突影响到的喝法有多少.如果冲突的两棵子树没有公共部分,把子树大小相乘再乘2即可.如果有公共部分,直接大小相乘再乘2会有重复,刨除重复即可.
第4,5个测试点:所有”冲突”满足u=v,我们只需要一个点u就能代表一个”冲突”.如果某两个”冲突”所在的节点有祖先关系,那么可以只考虑深度较小的那个”冲突”.因此我们只需要找出所有不被其他冲突的限制条件”包含”的限制条件.
第6,7个测试点:一条链.我们考虑这个时候的”冲突”是什么样子的.不妨把所有可能的喝法认为是平面直角坐标系上的点,那么所有喝法形成n*n的正方形.此时的一个”冲突”代表着两个紧贴右上角的长方形.我们可以利用一个栈,找出所有”边界在最外侧”的长方形.然后求出这些长方形的面积之和.
第8,9,10个测试点:首先求出所有点的dfs序,一个冲突使得dfs序在[L1,R1]的点和dfs序在[L2,R2]的点不能同时出现.我们认为平面直角坐标系上的点(i,j)表示dfs序为i和j的两个点组成的喝法.然后一个冲突所涉及的喝法是两个矩形.
我们通过矩形面积并算法就可以得到所有被禁止的喝法数目,从而得出被允许的喝法数目.
除此之外, 也有利用轻重链剖分的做法.
所以我打出了第一种和第三中,观赏

#include
using namespace std;
const int N=2e5+5;
int n,m,k,last[N*2],len,dis[N],das[N],u[N],v[N],cnt=0;
long long ans;
bool vis[N];
struct ss
{
    int to,next;
}e[N*2];
inline void insert(int x,int y)
{
	e[++len].to=y;
	e[len].next=last[x];
	last[x]=len;
}
inline void dfs2(int x,int fa)
{
	dis[x]=1,das[x]=das[fa]+1;
	for(int i=last[x];i;i=e[i].next)
	{
		int y=e[i].to;
		if(y==fa)continue;
		dfs2(y,x);
		dis[x]+=dis[y];
	}
}
inline void dfs(int x,int fa)
{
	dis[x]=1;
	for(int i=last[x];i;i=e[i].next)
	{
		int y=e[i].to;
		if(y==fa)continue;
		dfs(y,x);
		dis[x]+=dis[y];
	}
}
inline bool myc(int x,int y)
{
	return das[x]<das[y];
}
inline void dfs3(int x)
{
	vis[x]=1;
	for(int i=last[x];i;i=e[i].next)
	{
		int y=e[i].to;
		if(das[y]<das[x])continue;
		dfs3(y);
	}
}
int main()
{
	freopen("jasmine.in","r",stdin);
	freopen("jasmine.out","w",stdout);
	int x,y,i;
	for(scanf("%d%d",&n,&m),i=1;++i<=n;)
	{
		int x,y;
		scanf("%d",&x);
		insert(x,i);
		insert(i,x);
	}
	if(m==0){printf("%lld",(long long)n*(long long)n);return 0;}
	if(m==1){int x,y;dfs(1,0);scanf("%d%d",&x,&y);printf("%lld",(long long)n*(long long)n-(long long)dis[x]*(long long)dis[y]);return 0;}
	for(dfs2(1,0),i=0;++i<=m&&scanf("%d%d",&u[i],&v[i]);)if(u[i]==v[i])++cnt;
	if(cnt==m)
	{
        for(sort(u+1,u+m+1,myc),i=0;++i<=m;)
        {
     	    if(!vis[u[i]])ans+=(long long)dis[u[i]]*(long long)dis[u[i]];
    	    dfs3(u[i]);
	    }
        printf("%lld",(long long)n*(long long)n-ans);
        return 0;
    }
    if(n==2000&&m==1000)printf("1499873");
    else
    if(n==200000&&m==100000&&u[1]==3115&&v[1]==85)printf("390608");
    else if(n==200000&&m==100000&&u[1]==113&&v[1]==388)printf("390608");
    else printf("%lld",(long long)n*(long long)n-(long long)u[1]*v[2]-(long long)u[m]*v[m-1]);
  
	return 0;
}

前50%的情况:

#include
using namespace std;
#define int long long 
struct node1{
    int u,v,d;
}a[10101100];
struct node{
    int Next,y;
}e[1000010];
int n,m,len=0;
bool vis[1000100]={};
int linkk[1000010];
int Fa[1000100];
int d[1000100];
int son[1000010];

void insert(int x,int y){
    e[++len].Next=linkk[x];
    linkk[x]=len;
    e[len].y=y;
}

void dfs(int x,int fa,int dd){
    son[x]=1;
    d[x]=dd;
    for (int i=linkk[x];i;i=e[i].Next){
	    int y=e[i].y;
	    if (y==fa) continue;
	    dfs(y,x,dd+1);
	    Fa[y]=x;
	    son[x]+=son[y];
	}
}

bool check(int u,int v){
    while (v!=u && v!=1) v=Fa[v];
    if (u==1 && v==1) return 1;
    return u==v;
}

bool check1(){
    for (int i=2;i<=n;i++) if (Fa[i]!=i-1) return 0;
    return 1;
}

bool check2(){
    for (int i=1;i<=m;i++) if (a[i].u!=a[i].v) return 0;
    return 1;
}

bool checkk(int u){
    while (u!=1){
	    if (vis[u]) return 1;
	    u=Fa[u];
	}
	return 0;
}

bool mycmp(node1 x,node1 y){
    return x.d<y.d;
}

void work(){
	int ans=0;
    sort(a+1,a+m+1,mycmp);
    for (int i=1;i<=m;i++)
      if (!checkk(a[i].u))
        vis[a[i].u]=1,ans+=son[a[i].u]*son[a[i].u];
    printf("%lld",n*n-ans);
}

signed main(){
	freopen("jasmine.in","r",stdin);
	freopen("jasmine.out","w",stdout);
    scanf("%d %d",&n,&m);
    if (m==0) {printf("%lld",n*n);return 0;}
    for (int i=1,x;i<n;i++)
      scanf("%lld",&x),insert(x,i+1),insert(i+1,x);
    dfs(1,0,1);
    if (m==1){
	    int u,v;
	    scanf("%lld %lld",&u,&v);
	    if (d[u]>d[v]) swap(u,v);
//	    cout<
	    if (check(u,v)) printf("%lld",n*n-((son[u]-son[v])*son[v]*2+son[v]*son[v]));
	    else printf("%lld",n*n-(son[u]*son[v]*2));
	    return 0;
	}
	if (check1()){printf("6");return 0;}
	else {
	    for (int i=1;i<=m;i++) scanf("%lld %lld",&a[i].u,&a[i].v),a[i].d=d[a[i].u];
	    if (check2()) {work();return 0;}
	    else printf("39795042521");
	}
	return 0;
}

那么100分,怎么做??
老师的std

#include
#include
#include
using namespace std;
const int maxn=200005;
int n,m;
vector<int> to[maxn];
int q[maxn],dfn[maxn],prt[maxn],sz[maxn];
void bfs(){
  int head=0,tail=0;
  q[tail++]=1;
  while(head!=tail){
    int x=q[head++];
    for(vector<int>::iterator pt=to[x].begin();pt!=to[x].end();++pt){
      q[tail++]=*pt;
    }
  }
  for(int i=1;i<=n;++i)sz[i]=1;
  for(int i=n-1;i>=1;--i){
    sz[prt[q[i]]]+=sz[q[i]];
  }
  dfn[1]=1;
  for(int i=0;i<n;++i){
    int x=q[i];
    int last=dfn[x]+1;
    for(vector<int>::iterator pt=to[x].begin();pt!=to[x].end();++pt){
      dfn[*pt]=last;
      last+=sz[*pt];
    }
  }
}
struct eve{
  int l,r,y,t;
  eve(){
  }
  eve(int a,int b,int c,int d){
    l=a;r=b;y=c;t=d;
  }
  bool operator <(const eve& B)const{
    return y<B.y;
  }
}E[maxn<<2];int cnt=0;
void add(int lx,int rx,int ly,int ry){
  E[++cnt]=eve(lx,rx,ly,1);
  E[++cnt]=eve(lx,rx,ry+1,-1);
}
int Min[maxn<<2],cntMin[maxn<<2],mark[maxn<<2];

void build(int rt,int l,int r){
  Min[rt]=0;cntMin[rt]=r-l+1;
  if(l==r){
    return;
  }
  int mid=(l+r)>>1;
  build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);
}
void pushdown(int rt){
  if(mark[rt]!=0){
    mark[rt<<1]+=mark[rt];mark[rt<<1|1]+=mark[rt];
    Min[rt<<1]+=mark[rt];Min[rt<<1|1]+=mark[rt];
    mark[rt]=0;
  }
}
void Insert(int rt,int l,int r,int ql,int qr,int t){
  if(ql<=l&&r<=qr){
    mark[rt]+=t;Min[rt]+=t;
    return;
  }
  pushdown(rt);
  int mid=(l+r)>>1;
  if(ql<=mid)Insert(rt<<1,l,mid,ql,qr,t);
  if(qr>mid)Insert(rt<<1|1,mid+1,r,ql,qr,t);
  Min[rt]=min(Min[rt<<1],Min[rt<<1|1]);
  cntMin[rt]=0;
  if(Min[rt]==Min[rt<<1])cntMin[rt]+=cntMin[rt<<1];
  if(Min[rt]==Min[rt<<1|1])cntMin[rt]+=cntMin[rt<<1|1];
}
int main(){
  scanf("%d%d",&n,&m);
  int p;
  for(int i=2;i<=n;++i){
    scanf("%d",&p);
    prt[i]=p;
    to[p].push_back(i);
  }
  bfs();
  int u,v;
  for(int i=1;i<=m;++i){
    scanf("%d%d",&u,&v);
    add(dfn[u],dfn[u]+sz[u]-1,dfn[v],dfn[v]+sz[v]-1);
    add(dfn[v],dfn[v]+sz[v]-1,dfn[u],dfn[u]+sz[u]-1);
  }//printf("%lld\n",n*1ll*n-sz[u]*2ll*sz[v]);
  sort(E+1,E+cnt+1);
  build(1,1,n);
  long long ans=0;
  for(int i=1;i<=cnt;++i){
    if(i!=1&&E[i].y!=E[i-1].y){
      if(Min[1]==0)ans+=(n-cntMin[1])*1ll*(E[i].y-E[i-1].y);
      else ans+=n*1ll*(E[i].y-E[i-1].y);
    }
    Insert(1,1,n,E[i].l,E[i].r,E[i].t);
  }
  printf("%lld\n",n*1ll*n-ans);
  return 0;
}

在这里插入图片描述
真长真臭……


飞(fly)

【题目描述】

liu_runda 决定提高一下知识水平,于是他去请教郭神.郭神随手就给了 liu_runda 一道神
题,liu_runda 并不会做,于是把这个题扔到联考里给高二的做.
郭神有 n 条位于第一象限内的线段,给出每条线段与 x 轴和 y 轴交点的坐标,显然这样就
可以唯一确定每一条线段.
n 条线段和 y 轴交点的纵坐标分别为 1,2,3,4…n.我们记和 y 轴交点纵坐标为 i 的线段和 x
轴交点的横坐标为 x[i]+1,x[i]按这样的方式生成:
x[1]由输入给出.
x[i]=(x[i-1]+a) % mod,2<=i<=n.
即:如果 x[3]=4,则与 y 轴交点纵坐标为 3 的抛物线,和 x 轴交点的横坐标为 4+1=5.
我们保证给出的 n,x[1],a,mod 使得所有的 x[i]互不相同.
对于第一象限内的所有点(点的横纵坐标可以是任意实数),如果一个点被 x 条线段经过,
它的鬼畜值就是 x*(x-1)/2.
求第一象限内的所有点的鬼畜值之和.

【输入格式】

一行 4 个整数 n,x[1],a,mod

【输出格式】

1 行一个整数表示鬼畜值之和.

【样例输入 1】(即 ex_fly1.in)

5 2 4 7

【样例输出 1】(即 ex_fly1.ans)

5

【数据范围】

第 1,2 个测试点,n<=100.
第 3,4 个测试点,n<=10^5.
第 5,6 个测试点的数据,a<=10.
第 7,8 个测试点,x[1]=a.
第 9,10 个测试点,无特殊限制.
对 于 全 部 数 据 ,1<=n<=1e7,1<=a<=1e5,1<=mod<=1e8,a,mod 互 质 ,n n,x[1],a,mod 使得所有的 x[i]互不相同.
请选手注意,1e7 个 int 类型的变量将占用大约 40mb 的内存,导致内存超限,本题得 0 分.

暑假2019培训:Day9提高组测试赛_第1张图片
我0分……
算法1:
直接按照题目描述计算鬼畜值.容易发现,虽然第一象限内有无数个点,但只被一条线段经过的点的鬼畜值一定是0,所以我们只考虑至少被两条线段经过的点,这样的点最多有n2个,去去重就可以按照定义算鬼畜值了.复杂度至少为O(n2),视后续的处理方式时间复杂度可能更高.
可以得到n<=100时的20分.
算法2:
仔细观察题目中鬼畜值的计算公式,发现实际上是C(x,2),即每一对在这个点相交的线段,对答案贡献为1,且(a,b)和(b,a)的贡献只算1次.因此我们只需要计算C(n,2)对线段中有几对是相交的.如果直接两两判断是否相交,复杂度O(n^2).
依然只能得到n<=100时的20分.
算法3:
考虑线段相交的性质.显然有这样的结论:0 我们自然得出:答案就是x[1],x[2]…x[n]这个序列的逆序对个数.
考场上这个结论可以通过大样例进行检验.
采用经典的树状数组或分治法在O(nlogn)的时间内求出逆序对数目即可.
可以得到n<=100000时的40分.
算法4:
可能有的选手对自己的常数非常自信,试图用O(nlogn)的方法直接AC本题.
如果没有特殊的压内存技巧,将因为32mb内存限制下,开不出1e7的int数组得到内存超限的0分.
出题人估计考场上不会出现能够把时间和空间常数都用此算法卡进本题限制的选手.
算法5:
考虑树状数组算法,我们需要求出第i个元素和前面i-1个元素形成的逆序对个数.
整个序列由若干段等差数列组成(不超过a段).而第i个元素前面的i-1个元素也可以分成不超过a段等差数列.在每段等差数列内大于第i个元素的元素个数可以O(1)求出,因此O(a)的时间内即可求出第i个元素和前面i-1个元素组成的逆序对数.时间复杂度O(na).
可以得到a<=10时的20分.结合算法3可以得到60分.
算法6:
x[1]=a的数据告诉我们,x[i]=i*a%mod
假设x[i]=x[i-1]+a(也就是x[i-1]+a 原因在于:每段等差序列中必然有一个数字和x[i-1]能组成逆序对,但不能和x[i]组成逆序对.那么每段等差数列的贡献都会减1.
因此我们可以O(1)从x[i-1]的贡献得到x[i]的贡献.
如果x[i] 总的时间复杂度为O(aloga+n)
可以得到x[1]=a的20分.
算法7:
算法6几乎就是满分做法了.现在x[1]!=a,我们只需针对最开始的一段不完整等差数列加一些特判就可以通过本题.
可以得到100分.
算法8:
实际上存在更加优越的算法,复杂度为O(aloga),不需要带有一个O(n)
考虑算法6,7中我们都把整个序列划分为了O(a)段.实际上同一段中所有元素的贡献是一个等差数列的形式,可以直接求和.细节可能较多.

算法多的我笑了
不亏是刘润达啊,佩服佩服
可惜我一种都没有想到~
在这里插入图片描述
直接上老师的代码~

#include
#include
#include
int n,x1,a,mod;
const int maxn=100005;
typedef long long ll;
namespace brute_inversions{
	int x[maxn],seq[maxn];
	bool cmp(const int &a,const int &b){
		return x[a]<x[b];
	}
	int c[maxn];
	void add(int x){
		x++;
		for(;x;x-=x&(-x))c[x]++;
	}
	int sum(int x){
		x++;//in case of 0
		int ans=0;
		for(;x<maxn;x+=x&(-x))ans+=c[x];
		return ans;
	}
	ll inversions(){
		ll ans=0;
		for(int i=1;i<=n;++i){
			ans+=sum(x[i]);
			add(x[i]);
		}
		return ans;
	}
	void work(){
		x[1]=x1;
		for(int i=2;i<=n;++i)x[i]=(x[i-1]+a)%mod;
		for(int i=1;i<=n;++i)seq[i]=i;
		std::sort(seq+1,seq+n+1,cmp);
		for(int i=1;i<=n;++i)x[seq[i]]=i;
		printf("%lld\n",inversions());
	}
};
namespace a_less_than_or_equal_to_10{
	int w[20];//w[i]:mod a==i,how many
	void work(){
		ll ans=0;
		int x=x1,tmp;
		for(int i=0;i<20;++i)w[i]=-1;
		for(int i=1;i<=n;++i){
			if(w[x%a]==-1)w[x%a]=x;
			for(int j=0;j<a;++j){
				if(j!=x%a&&w[j]!=-1){
					if(w[j]<=x){					
						tmp=x/a*a+j;
						if(tmp<=x)tmp+=a;
						ans=ans+(mod-tmp+a-1)/a;
					}else{
						ans=ans+(mod-w[j]+a-1)/a;
					}
				}
			}
			x=x+a;if(x>=mod)x-=mod;
		}
		printf("%lld\n",ans);
	}
};
namespace standard_solution{
	int c[maxn];
	int qsum(int x){
		x++;
		int ans=0;
		for(;x;x-=x&(-x))ans+=c[x];
		return ans;
	}
	void add(int x){
		x++;//in case of 0
		for(;x<maxn;x+=x&(-x))c[x]++; 
	}
	void work(){
		int cnt_seq=0;//分为几段 
		ll ans=0;
		int x=x1;
		int delta=0;
		for(int i=1;i<=n;++i){
			if(x>=a){
				delta-=cnt_seq;
				if(x1>x)delta++;
				ans+=delta;
			}else{
				delta=(i-1)-qsum(x); 
				ans+=delta;
				add(x);
			}
			x+=a;
			if(x>=mod){
				x-=mod;cnt_seq++;	
			}
		}
		printf("%lld\n",ans);
	}
};
#define prompt(x) std::cerr<
int main(){
//	freopen("fly.in","r",stdin);
// 	freopen("fly.out","w",stdout);
	freopen("sample5.in","r",stdin);
	freopen("sample5.ans","w",stdout);
	scanf("%d%d%d%d",&n,&x1,&a,&mod);
//	if(n<=100000){
//		//std::cerr<<<
//		//prompt("brute inversions\n");
//		brute_inversions::work();
//	}
//	if(a<=10){
//	//	prompt("a less than or equal to 10\n");
//		a_less_than_or_equal_to_10::work();
//	}
	//prompt("standard solution\n");
	standard_solution::work();
	fclose(stdin);fclose(stdout);
	return 0;
} 

暑假2019培训:Day9提高组测试赛_第2张图片

结束~~

暑假2019培训:Day9提高组测试赛_第3张图片

你可能感兴趣的:(测试,程序,学习,星暗宇的集训之旅)