今天早上好困啊,状态一点都没有,10分,惨不忍睹的排名就不说了,今天的题目都挺考验思维的,还是要多多联系
给出一个N个节点的无根树,每条边有非负边权,每个节点有三种颜色:黑,白,灰。
一个合法的无根树满足:树中不含有黑色结点或者含有至多一个白色节点。
现在希望你通过割掉几条树边,使得形成的若干树合法,并最小化割去树边权值的和。
第一行一个正整数N,表示树的节点个数。
第二行N个整数Ai,表示i号节点的颜色,0 表示黑色,1表示白色,2表示灰色。
接下来N-1行每行三个整数Xi Yi Zi,表示一条连接Xi和Yi权为Zi的边。
输出一个整数表示其最小代价。
5
0 1 1 1 0
1 2 5
1 3 3
5 2 5
2 4 16
10
花费10的代价删去边(1, 2)和边(2, 5)。
20%的数据满足N≤10。
另外30%的数据满足N≤100,000,且保证树是一条链。
100%的数据满足N≤300,000,0≤Zi≤1,000,000,000,Ai∈{0,1,2}。
比赛思路: 懵……知道是树形DP,但脑子里一片空白
正解思路: 设 f [ i ] [ 0 ] f[i][0] f[i][0]表示以i为根的子树,除去已经割去的部分,满足没有黑色节点的最小代价, f [ i ] [ 1 ] f[i][1] f[i][1]表示没有白色节点的最小代价, f [ i ] [ 2 ] f[i][2] f[i][2]表示有1个白色节点的最小代价,显然最后输出 m i n ( f [ 1 ] [ 0 ] , f [ 1 ] [ 1 ] , f [ 1 ] [ 2 ] ) min(f[1][0],f[1][1],f[1][2]) min(f[1][0],f[1][1],f[1][2])
那么动态转移方程就是 (由于本人太懒,这里用一下某大佬的图):
另外听说这题用系统栈dfs会爆,所以我就跟风打了人工栈(为虾米有些大佬用系统栈也过了?)
反思 : 还是那句话,关于树的知识点要加强
#include
using namespace std;
struct arr
{
long long next,to,w;
}edge[600005];
const long long inf=1e17;
long long n,a[300005],f[300005][3],cnt,d[300005],head[300005],fa[300005];
bool bz[300005];
void add(long long u,long long v,long long len)
{
edge[++cnt].to=v;
edge[cnt].w=len;
edge[cnt].next=head[u];
head[u]=cnt;
}
void check()
{
long long h=0,t=1;
memset(bz,false,sizeof(bz));
d[1]=1;
bz[1]=true;
while (h<t)
{
long long now=d[++h],i,j;
for (i=head[now];i;i=edge[i].next)
{
long long v=edge[i].to;
if (!bz[v])
{
d[++t]=v;
bz[v]=true;
fa[v]=now;
}
}
}
long long i,j;
for (j=t;j>=1;j--)
{
long long now=d[j];
if (a[now]==0) f[now][0]=inf;
if (a[now]==1) f[now][1]=inf;
long long tot=-inf;
for (i=head[now];i;i=edge[i].next)
{
long long v=edge[i].to;
if (v!=fa[now])
{
if (f[now][0]!=inf) f[now][0]+=min(f[v][0],edge[i].w+min(f[v][1],f[v][2]));
if (f[now][1]!=inf) f[now][1]+=min(f[v][1],edge[i].w+min(f[v][0],f[v][2]));
if (a[now]==1) f[now][2]+=min(f[v][1],edge[i].w+min(f[v][0],f[v][2]));
else tot=max(tot,min(f[v][1],edge[i].w+min(f[v][0],f[v][2]))-f[v][2]);
}
}
if (a[now]!=1) f[now][2]=f[now][1]-tot;
}
}
int main()
{
freopen("tree2.in","r",stdin);
freopen("tree2.out","w",stdout);
scanf("%lld",&n);
long long i;
for (i=1;i<=n;i++)
scanf("%lld",&a[i]);
for (i=1;i<n;i++)
{
long long x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
check();
printf("%lld\n",min(f[1][0],min(f[1][1],f[1][2])));
return 0;
}
3 4
2 1 E 1 2 N
2 1 N 1 1 N
3 1 N 2 1 N
2 2 N 1 1 N
YES
YES
NO
NO
比赛思路: 题目理解错误,懵……
正解思路: 将模型转化为 n ∗ n n*n n∗n个格子,用 ( n + 1 ) ∗ ( n + 1 ) (n+1)*(n+1) (n+1)∗(n+1)个点将其连接,对于每个点我们可以按顺序复制一个数给它,之后把外面一圈点的父亲全部设为1,之后每执行题目中删掉一条边的命令是就将这条边所对应的两个点判断一下是否联通,如果是则输出NO,否则输出YES再合并
反思: 读题能力要加强,学过的知识要广泛使用
#include
using namespace std;
int a1,b1,a2,b2,n,k,cnt,a[705][705],fa[500005],bz;
char c1,c2;
int find(int k)
{
if (k==fa[k]) return k;
else return fa[k]=find(fa[k]);
}
int main()
{
freopen("sox.in","r",stdin);
freopen("sox.out","w",stdout);
int i;
scanf("%d%d",&n,&k);
for (i=1;i<=n+1;i++)
{
int j;
for (j=1;j<=n+1;j++)
{
a[j][i]=++cnt;
if (i==1 || j==1 || j==n+1 || i==n+1) fa[a[j][i]]=1;
else fa[a[j][i]]=cnt;
}
}
bz=1;
while (k--)
{
scanf("%d %d %c %d %d %c",&a1,&b1,&c1,&a2,&b2,&c2);
if (bz==2)
{
a1=a2;
b1=b2;
c1=c2;
}
int t1,t2;
if (c1=='E')
{
int t1,t2;
t1=find(a[a1+1][b1]);
t2=find(a[a1+1][b1+1]);
if (t1!=t2)
{
printf("YES\n");
fa[t1]=t2;
bz=1;
}
else
{
printf("NO\n");
bz=2;
}
}
else
{
int t1,t2;
t1=find(a[a1][b1+1]);
t2=find(a[a1+1][b1+1]);
if (t1!=t2)
{
printf("YES\n");
fa[t1]=t2;
bz=1;
}
else
{
printf("NO\n");
bz=2;
}
}
}
return 0;
}
小A非常喜欢所有押韵的东西,他认为两个单词押韵当且仅当他们的公共后缀的长度和两个单词中最长的单词的长度相等,或者是最长的单词的长度减一。也就是说LCS(A,B)>=max(|A|,|B|)-1。
有一天,小A读了一个有N个单词的小故事,他想知道,如果挑选一些故事里出现的单词组成一个新的单词序列,能组成的最长的满足以下条件的单词序列的长度是多少:单词序列中任意相邻的两个单词都押韵。(每个单词最多只能用一次)
第一行包含一个正整数N(1<=N<=500000),表示单词的个数。
接下来N行,每行一个由英文小写字母构成的单词,表示原故事里的单词。输入保证所有单词都不一样,并且他们的总长度不超过3,000,000。
输出满足条件的最长单词序列的长度。
输入1:
4
honi
toni
oni
ovi
输入2:
5
k
ask
psk
krafna
sk
输入3:
5
pas
kompas
stas
s
nemarime
输出1:
3
输出2:
4
输出3:
1
有30%的数据,有N<=18。
比赛思路: 第一眼想到要用trie(字典树),然后……然后就懵……最后只能打部分分的暴力(结果才拿了10分)
正解思路: 倒着建一个trie,也就是把每个字符串倒着放进去,之后在求一下每个节点儿子的最长链和次长链更新答案就行了
反思: 不得不说trie都差点被我遗忘了,学过的知识点要及时温习
#include
using namespace std;
int tree[2000005][27],num[2000005],f[2000005],n,ans;
char s[2000005];
int main()
{
freopen("rhyme.in","r",stdin);
freopen("rhyme.out","w",stdout);
scanf("%d",&n);
int i,len,t,tot=0;
for (i=1;i<=n;i++)
{
t=0;
cin>>s+1;
len=strlen(s+1);
int j;
for (j=len;j>=1;j--)
{
if (!tree[t][s[j]-96]) tree[t][s[j]-96]=++tot;
t=tree[t][s[j]-96];
}
num[t]++;
}
for (i=tot;i>=0;i--)
{
int sum=0,sum1=0;
f[i]=num[i];
int j;
for (j=1;j<=26;j++)
{
if (num[tree[i][j]])
{
f[i]++;
if (sum<f[tree[i][j]]-1)
{
sum1=sum;
sum=f[tree[i][j]]-1;
}
else if (sum1<f[tree[i][j]]-1) sum1=f[tree[i][j]]-1;
}
}
int summ=sum;
if (sum1)
{
sum+=sum1;
ans=max(ans,sum+f[i]);
}
f[i]+=summ;
ans=max(ans,f[i]);
}
printf("%d\n",ans);
return 0;
}