这场unr了,感觉好多几何题数学题.
题目翻译来自牛客群群友
A.Don't Starve(DP)
思路:我们可以把所有的边先处理出来,那么我们走的路线必定包含在其中.设f[x]为到达当前点x的最长的路径长度,初始化f[0]=0.然后针对于每一条边,我们都需要去更新f数组,但是不能直接去更新.因为我们直接更新的话就会直接把上一层的覆盖,而上一层的状态要保留去更新其他节点.比如我们从3到1的一条边更新了f[1],接下来要去用1到4的边去更新f[4],就会发现f[1]就不再是上一层的f[1]而是已经被更新的f[1],这样是不行的,就去采用一个中间变量数组g.
先让
g[edge[k].v]=max(g[edge[k].v],f[edge[k].u]+1);
用当前遍历的边更新上一层的情况并且保留结果,然后再
f[edge[k].v]=max(g[edge[k].v],f[edge[k].v]);
更新所有情况赋值即可.最后结果就是f中的最大值.
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N =2e3+10,mod=998244353;
int x[N],y[N],f[N],g[N];
struct node
{
int len,u,v;
};
vector<node>edge;
bool cmp(node a,node b)
{
return a.len>b.len;
}
//从长到短排序之后,就会符合题目条件,遍历的边长度逐渐缩短
void solve()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>x[i]>>y[i],f[i]=-1e18,g[i]=-1e18;
f[0]=0;
for(int i=0;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j)
edge.push_back({(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]),i,j});
sort(edge.begin(),edge.end(),cmp);
for(int i=0,j=0;i<edge.size();i=j)
{
for(j=i;j<edge.size()&&edge[i].len==edge[j].len;j++);
//确定相等同长度的边的范围,方便之后遍历
for(int k=i;k<j;k++)
g[edge[k].v]=-1e18;
for(int k=i;k<j;k++)
g[edge[k].v]=max(g[edge[k].v],f[edge[k].u]+1);
for(int k=i;k<j;k++)
f[edge[k].v]=max(g[edge[k].v],f[edge[k].v]);
}
int maxx=0;
for(int i=1;i<=n;i++)
maxx=max(maxx,f[i]);
cout<<maxx<<endl;
return ;
}
signed main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
solve();
return 0;
}
D.Birds in the tree (树形dp)
对于这个题我们要对于度数为1的节点分情况讨论,确定此次度数为1的点是什么颜色的情况,跑两次树形dp.f[x]的含义是x节点的子图有多少种符合题目的情况.
那么我们进行分类讨论.
1.如果当前节点(父节点)等于你此次遍历时确定的颜色.那么它就可以作为边界,也就是说它可以作为只含有一个度数的点存在.此时需要考虑的情况,根据乘法原理,就是对于所有子树的可形成子图的个数+1的乘积(这里+1是因为我们可以不选这一颗子树),即为下列式子:
(-1是减去什么都不选的空图)
但是还要注意,因为可以所有的子树都不选,只留一个父亲节点(假设所有子树都不选的话只有一个点也可以符合条件,因为这是子图,我们可以保留一条边)所以还需要+1;
2.假设当前的父亲节点颜色不符合此次遍历所确定的颜色,那么他就不能作为端点(度数为1的点),必须选取两个及以上的符合条件的子树的子图才可以进行方案计算,所以式子就是:
这里的f[v]的求和的方案数是减去的让当前父亲节点作为端点的情况,这样计算就得到了他链接两个合法子图的所有方案数了.
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N =3e5+10,mod=1e9+7;
int f[N],tot=0,h[N],ans=0;
string s;
struct node
{
int to,ne;
}edge[2*N];
void add(int x,int y)
{
edge[++tot].to=y;
edge[tot].ne=h[x];
h[x]=tot;
return ;
}
void dfs(int x,int fa,int col)
{
int mul=1,sum=0;
for(int i=h[x];i!=-1;i=edge[i].ne)
{
int j=edge[i].to;
if(j==fa)
continue;
dfs(j,x,col);
mul=mul*(f[j]+1)%mod;
sum=(sum+f[j])%mod;
}
f[x]=((s[x]==col+'0')+mul-1+mod)%mod;
if((s[x]==col+'0'))
ans=(ans+f[x])%mod;
else
ans=(ans+f[x]-sum+mod)%mod;
return ;
}
void solve()
{
memset(f,0,sizeof f);
memset(h,-1,sizeof h);
int n,u,v;
cin>>n>>s;
s='z'+s;
for(int i=1;i<n;i++)
{
cin>>u>>v;
add(u,v);
add(v,u);
}
dfs(1,-1,1);
memset(f,0,sizeof f);
dfs(1,-1,0);
cout<<ans<<endl;
return ;
}
signed main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
solve();
return 0;
}
G.KFC Crazy Thursday(回文自动机||马拉车算法&&差分前缀和)
思路:manacher板子,该算法得到的p数组就是以该点为中心,回文串的半径长度(因为有一些预处理,可以学习一下马拉车再看).那么我们只需要每次更新回文串的时候,确定了该处的后半区间都是回文串,直接用差分进行区间修改,表示这个回文的后半段区间都是某个回文串的结尾.然后跑一遍前缀和就可以知道每个字母结尾的回文串有多少个.(用的区间修改,但是最后只需要查询一次,果断差分加前缀和)最后统计即可.
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2000005;
string s,s1="";
int p[N],num[N];
int manacher(int n)
{
int mr=0,mid=0,sum=0;
p[0]=0;
for(int i=1;i<n;i++)
{
if(i<mr)
p[i]=min(p[mid*2-i],mr-i);
else
p[i]=1;
while(s1[i-p[i]]==s1[i+p[i]])
p[i]++;
if(i+p[i]>mr)
{
mr=i+p[i];
mid=i;
}
num[i]+=1;
num[i+p[i]]-=1;
}
for(int i=1;i<=n+1;i++)
{
cout<<num[i]<<" ";
num[i]+=num[i-1];
cout<<s1[i]<<" "<<num[i]<<endl;
}
return sum-1;
}
void solve()
{
int n;
cin>>n>>s;
s1+='$';
for(int i=0;i<s.size();i++)
{
s1+="#";
s1+=s[i];
}
s1+='#';
s1+='^';
manacher((int)s1.size());
int k=0,f=0,c=0;
for(int i=0;i<s1.size();i++)
{
if(s1[i]=='k')
k+=num[i];
if(s1[i]=='f')
f+=num[i];
if(s1[i]=='c')
c+=num[i];
}
cout<<k<<" "<<f<<" "<<c<<endl;;
return ;
}
signed main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
solve();
return 0;
}