目录
I、买一送一
theme:n个商店,标号1~n,每个商店卖标号为ai的商品(不同店可能卖相同商品),给出这些商店间的单向连接关系,求从商店1走到商店i(2<=i<=n),在两不同的店各买一个商品的二元组的不同(x,y)个数f(i)。
solution:首先要清楚这是一个图论题。先用临街表存点和边。由题意可知发f(i)=f(i-1)+curCnt-preCnt;即将f(i)分解为第二个商品在商店i买和不在商店i买两种情况,不在则为f(i-1),在则为以1到n的某条路径中不相同商品作为第一个购买商品,即curCnt,又在该条路径上可能已存在商品ai了,这时就要相应减去以上一个ai作为第二个商品的个数,即preCnt。
模型其实是以结点1为根节点的一棵树,所以编号i可能在树上不止一个结点,出现次数取决于从1到i的路径条数。所以结果加起来即可
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define far(i,t,n) for(int i=t;i<n;++i)
vector<int>v[100010];
int a[100010];
ll ans[100010];
int vis[100010];
int h[100010];
int tot;
void dfs(int t)
{
for(int i=0;i<v[t].size();++i)
{
int x=v[t][i];
ans[x]=ans[t]+tot-h[a[x]];
int pre=h[a[x]];
h[a[x]]=tot;
int flag=0;
if(!vis[a[x]])
tot++,flag=1;
vis[a[x]]++;
dfs(x);
h[a[x]]=pre;
if(flag)
tot--;
vis[a[x]]--;
}
}
int main()
{
int n;
while(~scanf("%d",&n))
{
for(int i=1;i<=n;++i)
v[i].clear();
memset(a,0,sizeof(a));
memset(ans,0,sizeof(ans));
memset(vis,0,sizeof(vis));
memset(h,0,sizeof(h));
for(int i=2;i<=n;++i)
{
int x;
scanf("%d",&x);
v[x].push_back(i);
}
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
tot=1;
vis[a[1]]=1;
dfs(1);
for(int i=2;i<=n;++i)
printf("%lld\n",ans[i]);
}
return 0;
}
/*
3
1 2
1 2 3
3
1 1
1 2 3
4
1 2 3
1 3 2 3
*/
H、千万别用树套树
theme:q次操作,每次要么插入一条端点在[l,r]的线段,要么查询包含[l,r],(即端点[x,y]满足x<=l<=r<=y)的线段条数。其中查询时满足r-l<=2
solution:对于插入操作,则把[l,r]区间内的每个顶点的值(a[i])+1,对于查询,当r-l==0时,条数为a[l];当r-l==1时,条数为a[l]-e[l],e[l]表示以l为终点的条数;当r-l==2时,条数为a[l+1]-e[l+1]-s[l+1]+eq[l+1],s[l]表示以l为起点的条数,eq[i]为同时以i为起点与终点的线段。
注意:插入[l,l]时,a[l]只加一次!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define far(i,t,n) for(int i=t;i<n;++i)
const int SIZE=100010;
ll t[4*SIZE];
ll bj[4*SIZE];
ll s[100010];
ll e[100010];
ll eq[100010];
void updata(int k,int l,int r)
{
int mid=(l+r)>>1;
//cout<<"updata: "<<bj[k]<<" ";
t[k<<1]+=(mid-l+1)*bj[k];
t[(k<<1)+1]+=(r-mid)*bj[k];
bj[k<<1]+=bj[k];
bj[(k<<1)+1]+=bj[k];
bj[k]=0;
//cout<<"updata: "<<bj[k<<1]<<" "<<bj[(k<<1)+1]<<endl;
}
void add(int root,int s,int e,int delta,int l,int r)
{
if(s<=l&&e>=r)
{
t[root]+=(r-l+1)*delta;
bj[root]+=delta;
return;
}
if(bj[root])updata(root,l,r);
int mid=(l+r)>>1;
if(s<=mid)
add(root<<1,s,e,delta,l,mid);
if(e>mid)
add((root<<1)+1,s,e,delta,mid+1,r);
t[root]=t[root<<1]+t[(root<<1)+1];
//cout<<"t[]"<<root<<": "<<t[root]<<endl;
}
ll query(int root,int i,int j,int l,int r)//i,j为要查询的区域
{
if(i<=l&&j>=r)
return t[root];
//cout<<bj[root]<<endl;
if(bj[root])updata(root,l,r);
//cout<<root<<" "<<bj[root<<1]<<" "<<bj[(root<<1)+1]<<endl;
int mid=(l+r)>>1;
ll m1=0,m2=0;
if(i<=mid)
m1=query(root<<1,i,j,l,mid);
if(j>mid)
m2=query((root<<1)+1,i,j,mid+1,r);
return m1+m2;
}
int main()
{
int n,q;
while(~scanf("%d%d",&n,&q))
{
memset(t,0,sizeof(t));
memset(bj,0,sizeof(bj));
memset(s,0,sizeof(s));
memset(e,0,sizeof(e));
memset(eq,0,sizeof(eq));
int t,l,r;
for(int i=0;i<q;++i)
{
scanf("%d%d%d",&t,&l,&r);
if(t==1)
{
add(1,l,r,1,1,n);
s[l]++;
e[r]++;
if(l==r)
eq[l]++;
}
else
{
if(r-l==0)
printf("%lld\n",query(1,l,l,1,n));
else if(r-l==1)
printf("%lld\n",query(1,l,l,1,n)-e[l]);
else
printf("%lld\n",query(1,l+1,l+1,1,n)-s[l+1]-e[l+1]+eq[l+1]);
}
}
}
return 0;
}
/*
4 100
1 3 3
1 1 4
1 3 3
1 2 3
1 3 4
1 1 3
1 2 4
*/
F、Use FFT
theme:给定两多项式P(x)=a0 + a1x + … + anxn 与 Q(x)=b0 + b1x + … + bmxm. 可求得:P(x)⋅Q(x)=c0 + c1x + … + cn + mxn + m 。给定 L 与 R.求 (cL + cL + 1 + … + cR) modulo (109 + 7) 。
solution:由多项式想乘性质,我们可以遍历a,对于每一个ai,找b中哪个区间的数与ai相加值在[l,r]范围内,所以问题转化为:1、求每一个ai在b中满足条件的区间[si,ei];2、求b中任意区间和(用前缀和)3、求ai*sum(b[s[i]],b[e[i]])。时间空间复杂度都为o(n)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define far(i,t,n) for(int i=t;i<n;++i)
ll a[500010];
ll b[500010];
ll s[500010];
ll e[500010];
ll sum[500010];
const ll mod=1e9+7;
int main()
{
int n,m,l,r;
while(~scanf("%d%d%d%d",&n,&m,&l,&r))
{
far(i,0,n+1)
scanf("%lld",&a[i]);
far(i,0,m+1)
scanf("%lld",&b[i]);
sum[0]=b[0]%mod;
far(i,1,m+1)
sum[i]=(sum[i-1]+b[i])%mod;
far(i,0,n+1)
{
int li=l-i;
int ri=r-i;
if(ri<0)
{
s[i]=e[i]=-1;
continue;
}
if(ri<m)
s[i]=li,e[i]=ri;
else if(m<li)
s[i]=e[i]=-1;
else
s[i]=li,e[i]=m;
if(s[i]<0)
s[i]=-1;
}
ll ans=0;
far(i,0,n+1)
{
int st=s[i],en=e[i];
ll cnt;
if(en==-1)
continue;
if(st==0||st==-1)
cnt=sum[en];
else
cnt=(sum[en]-sum[st-1]+mod)%mod;
ans=(ans+ (a[i]%mod*cnt)%mod) %mod;
}
printf("%lld\n",ans);
}
return 0;
}
D:卖萌表情
//✔48ms(1000ms)
/*theme:已知以下 4 种都是卖萌表情(空白的部分可以是任意字符。竖线是便于展示的分隔符,没有实际意义):
^ ^ | ^ | < | >
v | v v | > | <
| | < | >
(多组)给出n行m列的字符矩阵,找出互不重叠的卖萌表情数量的最大值.互不重叠的意思是每个字符只属于至多一个卖萌表情。
1≤n,m≤1000,矩阵只包含 ^, v, <, > 4 种字符.n × m 的和不超过 2×10^6.
*
*
*
*
*
solution:可分为两组分开算(左边两种和右边两种互不影响),由于遍历时从左上开始,右边两种没有冲突情况,所以只需遍历求个数即可
而左边两种可能存在^<^<^<^<^最优解,因为遍历是是以'^'为基准找下一行'v'匹配,但 ^ 比^ ^的'v'更靠左,即对v的利用率越高
<v<v<v<v< v v v
由贪心思想,应优先考虑第二种再选第一种,注意选中后要标记为已选(可改为其他符号),避免重复
*/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
char a[1100][1100];
int n,m;
int solve1()
{
int cnt=0;
for(int i=0;i<n-1;++i)
for(int j=0;j<m-1;++j)
if(a[i][j]=='^')
{
if(j-1>=0&&a[i+1][j-1]=='v'&&a[i+1][j+1]=='v')
++cnt,a[i+1][j-1]='0',a[i+1][j+1]='0';
else if(j+2<m&&a[i][j+2]=='^'&&a[i+1][j+1]=='v')
++cnt,a[i][j+2]='0',a[i+1][j+1]='0';
}
return cnt;
}
int solve2()
{
int cnt=0;
for(int i=0;i<n-2;++i)
for(int j=0;j<m;++j)
if(a[i][j]=='<')
{
if(i+2<n&&j<m-1&&a[i+1][j+1]=='>'&&a[i+2][j]=='<')
++cnt,a[i+1][j+1]='0',a[i+2][j]='0';
}
else if(a[i][j]=='>')
{
if(i+2<n&&a[i+1][j-1]=='<'&&a[i+2][j]=='>')
++cnt,a[i+1][j-1]='0',a[i+2][j]='0';
}
return cnt;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
for(int i=0;i<n;++i)
scanf("%s",a[i]);
//for(int i=0;i<n;++i)
// printf("%s",a[i]),putchar('\n');
int ans1=solve1();
int ans2=solve2();
//cout<<ans1<<" "<<ans2<<endl;
int ans=ans1+ans2;
printf("%d\n",ans);
}
}
/*
2 4
^^^^
>vv<
2 4
vvvv
>^^<
4 2
v>
<>
<>
^>
3 4
^>^>
<v>v
>>>>
2 9
^<^<^<^<^
<v<v<v<v<
2
0
2
2
3
*/