正难则反,直接枚举横行,枚举右边界,如果相同,则会对后面以及它本身统计产生 \(1\) 的贡献,我们直接开个桶统计一下。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1000+107;
int n,m;
int a[N][N],cnt[N*N];
int sum;
int read()
{
int f=1,s=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){s=(s<<1)+(s<<3)+(c^48);c=getchar();}
return f*s;
}
signed main()
{
freopen("colorful.in","r",stdin);
freopen("colorful.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
a[i][j]=read();
}
}
sum=n*(n+1)*m*(m+1)/4;
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
for(int k=1;k<=m;k++)
{
if(a[i][k]==a[j][k])
{
++cnt[a[i][k]];
ans+=cnt[a[i][k]];
}
}
for(int k=1;k<=m;k++) cnt[a[i][k]]=0;
}
}
printf("%lld",sum-ans);
}
双指针统计,不难发现值域很大,操作很少,我们发现一段区间更新后这一段时间的值不会立刻再发生改变,类似于莫队的移动边界到查询点的操作,我们差分处理一下,然后排序,直接模拟移动即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=4e6+107;
const int mod=1e9+7;
int n,m,s,t;
struct lmy
{
int x,pos,val;
}a[N];
bool comp(lmy a,lmy b){return a.pos==b.pos?a.val<b.val:a.pos<b.pos;}
int p[N],g[N];
int read()
{
int f=1,s=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){s=(s<<1)+(s<<3)+(c^48);c=getchar();}
return f*s;
}
int qpow(int a,int b)
{
int ans=1;
while(b)
{
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b=b>>1;
}
return ans;
}
signed main()
{
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
n=read(),m=read(),s=read(),t=read();
p[m*2+1]=s,p[m*2+2]=t+1;
for(int i=1;i<=m;i++)
{
int x=read(),l=read(),r=read();
p[i]=l,p[i+m]=r+1;
a[i]={x,l,1},a[i+m]={x,r+1,-1};
}
sort(p+1,p+1+2*m+2),sort(a+1,a+1+2*m,comp);
// int len=unique(p+1,p+1+2*m+2)-(p+1);
int j=0; int ans=1; int snum=n;
for(int i=1;i<m*2+2;i++)
{
while(j<m*2&&a[j+1].pos<=p[i])
{
j++;
int x=a[j].x;
int d=a[j].val;
if(g[x]>0&&g[x]+d<=0) snum++;
if(g[x]<=0&&g[x]+d>0) snum--;
g[x]+=d;
}
ans=ans*qpow(snum,p[i+1]-p[i])%mod;
}
printf("%lld",ans);
}
题解挺清楚的
计数DP转概率DP,首先将 \(i\) 向 \(b_i\) 建一条边,很显然 \(n\) 一定处于一颗基环树中,我们直接 \(dfs\) 找出环上最小的节点,接着枚举断边还是不断边,跑DP转移即可。我们设 \(f_i\) 表示 \(i\) 节点为猫的概率。 \(u\) 为当前节点, \(v\) 为子节点。
转移:
\(f_v=f_u*f_v\)
\(f_v=f_u*(1-f_v)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e4+107;
const int mod=1e9+7;
const int inv2=500000004;
int n,root;
int b[N],vis[N];
char s[N];
int f[N];
int read()
{
int f=1,s=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){s=(s<<1)+(s<<3)+(c^48);c=getchar();}
return f*s;
}
int qpow(int a,int b)
{
int ans=1;
while(b)
{
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b=b>>1;
}
return ans;
}
int fa[N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void merge(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy) fa[fx]=fy;
}
int dfs1(int x)
{
vis[x]=1;
int y=b[x];
return (vis[y]==1)?x:dfs1(y);
}
void dfs(int x,int &root)
{
root=min(root,x);
vis[x]=1;
int y=b[x];
if(vis[y]==0) dfs(y,root);
}
signed main()
{
freopen("experiment.in","r",stdin);
freopen("experiment.out","w",stdout);
int t=read();
while(t--)
{
memset(vis,0,sizeof vis);
int ans=0,sum=0;
n=read();
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++) scanf(" %c",&s[i]);
for(int i=1;i<=n;i++)
{
b[i]=read();
merge(i,b[i]);
sum+=(s[i]=='?');
}
for(int i=1;i<=n;i++)
{
if(find(i)==fa[n])
{
root=dfs1(i);
break;
}
}
memset(vis,0,sizeof vis);
dfs(root,root);
int num=0;
for(int j=0;j<=1;j++)
{
for(int k=0;k<=1;k++)
{
for(int i=1;i<=n;i++)
{
if(s[i]=='C') f[i]=1;
if(s[i]=='.') f[i]=0;
if(s[i]=='?') f[i]=inv2;
}
for(int i=1;i<=n;i++)
{
int y=b[i];
if(i==root)
{
if(j==1)
{
if(k==1) num=f[i]*f[y]%mod;
if(k==0) num=f[i]*(1-f[y]+mod)%mod;
}
if(j==0)
{
if(k==1) num=(1-f[i]+mod)%mod*f[y]%mod;
if(k==0) num=(1-f[i]+mod)%mod*(1-f[y]+mod)%mod;
}
f[i]=j;
f[y]=k;
}
int tmp=f[i];
f[i]=tmp*f[y]%mod;
f[y]=(f[y]+((1-f[y]+mod)%mod)*tmp%mod)%mod;
}
ans=(ans+num*f[n]%mod)%mod;
}
}
printf("%lld\n",ans*qpow(2,sum)%mod);
}
}