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;
}