zoukankan      html  css  js  c++  java
  • 2021.08.23 膜你赛

    2021.08.23 膜你赛

    count

    Description

    问有几个无序二元组 \((x, y)\) 满足 \(x y ≡ 1 \pmod p\), \(0 ≤ x < P, 0 ≤ y <P\)

    无序二元组是指,如果 \(P = 10\)\((3, 7)\)\((7, 3)\) 只算一次。

    Solution

    首先根据 \(xy \equiv 1\pmod p\) 可以得 \(x \equiv \frac{1}{y} \pmod p\) ,\(\frac{1}{y}\) 就是 \(x\)\(\mod p\) 意义下的逆元。一个数有逆元当且仅当 \(x,p\)​ 互质,因此我们可以通过求出 \(\phi\) 函数来求解部分答案。

    我们发现,答案中的一些数是可以两两配对的,一个数不可以两两配对当且仅当他是一个平方数,如果可以配对,就不满足无序数对的要求,我们可以先减去平方数,将剩下的数减半后再加上原来的数 (Ans-js)>>1+js ,其实通分之后和题解的答案一样,都等于 (Ans+js)>>1

    code

    /*
    * @Author: smyslenny
    * @Date:    2021.08.
    * @Title:   
    * @Main idea:
    */
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <iomanip>
    #include <cstring>
    #include <cstdlib>
    #include <queue>
    #include <vector>
    
    #define int long long
    #define INF 0x3f3f3f3f
    #define orz cout<<"LKP AK IOI\n"
    #define MAX(a,b) (a)>(b)?(a):(b)
    #define MIN(a,b) (a)>(b)?(a):(b)
    
    using namespace std;
    const int mod=998244353;
    const int M=5e7+5;
    int P,Ans;
    int read()
    {
    	int x=0,y=1;
    	char c=getchar();
    	while(c<'0' || c>'9') {if(c=='-') y=0;c=getchar();}
    	while(c>='0' && c<='9') { x=x*10+(c^48);c=getchar();}
    	return y?x:-x;
    }
    int get_phi(int n) {
        int res=n,a=n;
        for(int i=2;i*i<=a;i++) if(a%i==0) {res=res/i*(i-1);while(a%i==0) a/=i; }
        if(a>1) res=res/a*(a-1);
        return res;
    }
    int js;
    signed main()
    {
    //	freopen("count.in","r",stdin);
    //	freopen("count.out","w",stdout);
    	P=read();
    	Ans=get_phi(P);
    	for(int i=0;i<P;i++)
    		if(i*i%P==1)
    		js++;
    	printf("%lld\n",(Ans+js)>>1);
    	return 0; 
    }// 
    	
    

    color

    Description

    给出平面上 \(n\) 个点,你要把每个点染成黑色或白色。要求染完之后,任
    意一条与坐标轴平行的直线上,黑白点数量差的绝对值小于等于 \(1\)

    Solution

    对于x坐标相同的点,我们把这些点两两配对,如果剩下点则不管,然后在每一对点之间连一条边。

    对于y坐标相等的点,我们也把这些点两两配对,如果剩下点则不管,然后在每一对点之间连一条边。

    最后对得到的图进行其实就是黑白染色就可以得到方案了。总的配对出来的图案就是一个个矩形,或者是两个点连成的线。

    Code

    /*
    * @Author: smyslenny
    * @Date:    2021.08.
    * @Title:   
    * @Main idea:
    */
    #incldue <bits/stdc++.h> 
    using namespace std;
    const int N=1e6+10;
    int col[N],n,lstu[N],lstv[N];
    void addedge(int u,int v){edge[++num].u=u;edge[num].v=v;edge[num].net=head[u];head[u]=num;}
    void dfs(int u,int color)
    {
    	col[u]=color;
    	for(int i=head[u];i;i=edge[i].net) if(col[edge[i].v]==-1) dfs(edge[i].v,color^1);
    }
    int main()
    {
    	n=read();
    	for(int i=1;i<=n;i++) col[i]=-1;//预处理 
    	for(int i=1,x,y;i<=n;i++)
    	{
    		x=read(),y=read();
    		if(lstu[x]) addedge(lstu[x],i),addedge(i,lstu[x]),lstu[x]=0;//围城矩形 
    		else lstu[x]=i;
    		if(lstv[y]) addedge(lstv[y],i),addedge(i,lstv[y]),lstv[y]=0;
    		else lstv[y]=i;
    	}
    	for(int i=1;i<=n;i++) if(col[i]==-1)dfs(i,0);//将颜色一直图上01 
    	for(int i=1;i<=n;i++) printf((col[i])?"0 ":"1 ");
    	return 0;
    }
    

    sequence

    Description

    给出一个 \(n\) 个数的序列,一个区间的价值是区间内最小值乘最大值的积。

    求所有区间价值和。答案对 \(998244353\) 取模。

    Solution

    理解了比较长时间才明白了这个方法。

    题解里 \(Substack2\) 的方法是在 \([l,r]\) 中找到最大值的位置,记为 \(a_{mid}\)​, 考虑左端点在 \([l,mid]\)​ 内,右端点在 \([mid,r]\) 内的区间的价值和。它们内部的最大值肯定就是 \(a_{mid}\)

    \([l,mid]\)​ 做后缀 \(\min\)​,对 \([mid,r]\)​ 做前缀 \(\min\)​,把结果记为 \(mn[i]\)​。即 \(l\leq i\leq mid\)​ 时,\(mn[i] =min \times min_{i-l}^{mid}a_i\)​ ,\(mid< i \leq r\)​ 时,\(m=min_{i=mid}^ra_i\)​ 。

    枚举左端点 \(i\)​​​,由于右半部分的 \(mn\)​​​ 是单调的,因为只有遇见更小的数才会变化,所以一定存在一个分界点 \(p\)​​​,使得 \(j ≤ p\)​​ 时 \(mn[j] ≥ mn[i]\)​,\(j>p\)\(mn[j] <mn[i]\)。又由于左半部分的 \(mn\) 也是单调的,所以左端点 \(i\) 移动时,分界点 \(p\) 的移动方向是不变的。这样就容易对每个左端点 \(i\) 都求出分界点 \(p\) 了。另外再求出 \(mn\) 的前缀和,就能求出左端点的区间的价值和了。

    然后再递归计算。

    正解也应用到了这样的思想。

    每次取 \(mid=\lfloor\frac{l+r}{2}\rfloor\),对最大值进行和最小值类似的操作(把前缀/后缀 \(max\)

    的结果记为 \(mx[i]\)​​​​​),求出最大值的分界点 \(q\)​​​​ 。再求出 \(mx\)​​ 的前缀和以及 \(mn\times mx\)​​ 的前缀和。\(p\)​ 和 \(q\)​​ 把右半部分划分成三个区间,对三个区间分别计算。利用求出来的前缀和。

    Code

    /*
    * @Author: smyslenny
    * @Date:    2021.08.
    * @Title:   
    * @Main idea:
    */
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <iomanip>
    #include <cstring>
    #include <cstdlib>
    #include <queue>
    #include <vector>
    
    #define int long long
    #define INF 0x3f3f3f3f
    #define orz cout<<"LKP AK IOI\n"
    #define MAX(a,b) (a)>(b)?(a):(b)
    #define MIN(a,b) (a)>(b)?(a):(b)
    
    using namespace std;
    const int MOD=998244353;
    const int M=5e5+5;
    int n,Ans; 
    int mp[M],ans,maxl[M],maxr[M],minl[M],minr[M],sum[M],sum_minl[M],sum_maxl[M];
    int read()
    {
    	int x=0,y=1;
    	char c=getchar();
    	while(c<'0' || c>'9') {if(c=='-') y=0;c=getchar();}
    	while(c>='0' && c<='9') { x=x*10+(c^48);c=getchar();}
    	return y?x:-x;
    }
    void solve(int l,int r)
    {
    	if(l>r)return;
    	if(l==r) { ans=(ans+mp[l]*mp[r])%MOD;return;}
    	int mid=l+r>>1;
    	solve(l,mid-1);solve(mid+1,r);
    	int s=0;
    	maxl[0]=maxr[0]=minl[0]=minr[0]=mp[mid]; 
    	for(int i=mid+1;i<=r;i++) maxr[i-mid]=max(maxr[i-mid-1],mp[i]),minr[i-mid]=min(minr[i-mid-1],mp[i]);//向后求出前缀最大值最小值 
    	for(int i=mid-1;i>=l;i--) maxl[mid-i]=max(maxl[mid-i-1],mp[i]),minl[mid-i]=min(minl[mid-i-1],mp[i]);//先前求出后缀最大值最小值 
    	sum[mid-l+1]=0;//清空,原来的答案都计入ans,已经没有用了 
    	for(int i=l;i<=mid;i++) sum[mid-i]=(sum[mid-i+1]+minl[mid-i]*maxl[mid-i])%MOD,sum_minl[mid-i]=(sum_minl[mid-i+1]+minl[mid-i])%MOD,sum_maxl[mid-i]=(sum_maxl[mid-i+1]+maxl[mid-i])%MOD;
    	//对于在左区间的每一个 i,求 min*max前缀和,min前缀和,max前缀和。以方便找到 p 点 
    	int now_minl=0,now_maxl=0;
    	for(int i=0;i<=r-mid;i++)//枚举右端点,在左区间寻找答案 
    	{
    		while(now_minl<=mid-l&&minl[now_minl]>=minr[i])now_minl++;//小于左边区间的长度 (第一个限制) 固定右端点,找到该最大值最小值能够延伸的区间 
    		while(now_maxl<=mid-l&&maxl[now_maxl]<=maxr[i])now_maxl++;//以 i 为右端点的最小值,左端点不能比他大,要不然最大值就要变化了 (第二个限制) 
    		ans=(ans+minr[i]*maxr[i]%MOD*min(now_minl,now_maxl))%MOD;//加上 x 个 min*max 的答案  所有区间重复的地方 
    		ans=ans+sum[max(now_minl,now_maxl)]; 
    		if(now_minl<now_maxl)ans=(ans+(sum_minl[now_minl]-sum_minl[now_maxl]+MOD)*maxr[i])%MOD;//超出来的部分 
    		else ans=(ans+(sum_maxl[now_maxl]-sum_maxl[now_minl]+MOD)*minr[i])%MOD;
    	}
    }	
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++) mp[i]=read();
    	solve(1,n);
    	printf("%lld\n",ans%MOD);
    	return 0;
    }
    			
    
  • 相关阅读:
    Wannafly 挑战赛12 E
    HIT ACM 2018春 week2 codeforces.com/gym/101652 题解
    Hihocoder [Offer收割]编程练习赛49 题目4 : 第K小先序遍历
    HDU
    ZOJ
    HYSBZ
    POJ
    HYSBZ
    POJ 2796 Feel Good 题解
    逆元基本知识
  • 原文地址:https://www.cnblogs.com/jcgf/p/15245333.html
Copyright © 2011-2022 走看看