C. Watching Fireworks is Fun
题意:
一条街道有
n
n
n个区域。 从左到右编号为
1
1
1到
n
n
n。 相邻区域之间的距离为
1
1
1。在节日期间,有
m
m
m次烟花要燃放。 第
i
i
i次烟花燃放区域为
a
i
a_i
ai,幸福属性为
b
i
b_i
bi,时间为
t
i
t_i
ti。
t
i
⩽
t
i
+
1
t_i⩽t_i+1
ti⩽ti+1
如果你在第
i
i
i次烟花发射时在
x
(
1
⩽
x
⩽
n
)
x(1⩽x⩽n)
x(1⩽x⩽n)处,你将获得幸福值
b
i
−
∣
a
i
−
x
∣
bi−|ai−x|
bi−∣ai−x∣(请注意,幸福值可能是负值)。
你可以在单位时间间隔内移动最多
d
d
d个单位,但禁止走出主要街道。 此外,您可以在初始时刻(时间等于1时)处于任意区域,并希望最大化从观看烟花中获得的幸福总和。
题解:dp+单调队列+滚动数组优化
如果时间重叠,就直接状态转移,否则就单调队列优化一下。
状态转移方程:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
k
]
+
b
i
−
∣
a
i
−
j
∣
dp[i][j]=dp[i-1][k]+b_i-|a_i-j|
dp[i][j]=dp[i−1][k]+bi−∣ai−j∣…
(
∣
j
−
k
∣
<
=
(
t
i
−
t
i
−
1
)
∗
d
)
(|j-k|<=(t_i-t_{i-1})*d)
(∣j−k∣<=(ti−ti−1)∗d)
#include<bits/stdc++.h>
#define mp make_pair
#define se second
#define fi first
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef long double ld;
const int N=5e5+10;
const int MAXN=20010;
const int INF=0x3f3f3f3f;
const ll INF64=0x3f3f3f3f3f3f3f3f;
const double eps=0.0000001;
const ll mod=1e9+7;
ll n,m,x,y,z,k,cnt,t,len;
ll a[N],b[N],ti[N];
ll dp[2][150005];///滚动数组优化
struct node
{
ll x,y;
}v[150005];
int main()
{
ll d;
scanf("%lld%lld%lld",&n,&m,&d);
for(int i=1;i<=m;i++)
{
scanf("%lld%lld%lld",&a[i],&b[i],&ti[i]);
}
ti[0]=ti[1];
int id=1;
for(int i=1;i<=m;i++,id=1-id)
{
if(ti[i]==ti[i-1])
{
for(int j=1;j<=n;j++)
{
dp[id][j]=dp[1-id][j]+b[i]-abs(a[i]-j);
}
continue;
}
int head=1,tail=0,k=1;
for(int j=1;j<=n;j++)
{
while(k<=n&&k<=(ti[i]-ti[i-1])*d+j)
///由于区间大小不固定,需要枚举一下右端点
{
while(head<=tail && v[tail].y<dp[1-id][k]) tail--;
v[++tail].y=dp[1-id][k],v[tail].x=k;
k++;
}
while(head<=tail&&j-v[head].x>(ti[i]-ti[i-1])*d) head++;
if(head<=tail)
dp[id][j]=v[head].y+b[i]-abs(a[i]-j);
}
}
ll ans=-INF64;
for(int i=1;i<=n;i++)
{
ans=max(ans,dp[1-id][i]);
}
printf("%I64d\n",ans);
}
C. Colorful Tree
题意:
给你一个包含
n
n
n个节点的树,用一个数字代表一种颜色,树上的路径权值为在这条路径上包含的颜 色数量,这棵树总共有
n
∗
(
n
−
1
)
/
2
n*(n-1)/2
n∗(n−1)/2条路径。求这棵树上所有路径的总权值。
题解:树形dp+容斥
如果正向想这道题就很麻烦,不好下手(反正我是不会做)。然后看了网上的博客和官方题解,采用的是逆向思维:先认为每个节点有所有的颜色,所以贡献就是
c
∗
n
∗
(
n
−
1
)
/
2
c*n*(n-1)/2
c∗n∗(n−1)/2 ,
c
c
c是颜色总数。然后减去每个颜色没有经过的路径,这样有一个好处,就是把不符合的路径分为一个一个的联通块,因此不符合的路径就是
c
o
u
n
t
∗
(
c
o
u
n
t
−
1
)
/
2
count*(count-1)/2
count∗(count−1)/2,
c
o
u
n
t
count
count表示不同颜色的节点数。其实仔细想想就是容斥的思想。
需要注意的就是不经过的路径分为两种来计算
1.与这个颜色的子叶节点中不同颜色的路径
2.与这个颜色的父亲节点中不同颜色的路径
参考博客
#include <bits/stdc++.h>
using namespace std;
const int N = 4e5+100;
typedef long long ll;
int c[N],vis[N];
vector<int> e[N];
ll sum[N];///sum[i]表示的是子叶节点的颜色为 i的子树大小
ll sizes[N];///子树大小
ll ans;
void dfs(int x,int y)
{
sizes[x]=1;
sum[c[x]]++;
ll pre=sum[c[x]];///pre表示计算完的节点个数
for(int i=0; i<e[x].size(); i++)
{
if(e[x][i]==y) continue;
dfs(e[x][i],x);
sizes[x]+=sizes[e[x][i]];
ll counts=sizes[e[x][i]]-(sum[c[x]]-pre);
///counts表示的是根节点到颜色为c[x] 的子叶节点间不同颜色的点的个数
///(sum[c[x]]-pre)为这棵子树的颜色为 c[x]的子叶节点的子树大小
ans=ans+(1LL*counts*(counts-1))/2;
sum[c[x]]+=counts;
pre=sum[c[x]];
}
}
int main()
{
int n,cas=1;
while(scanf("%d",&n)!=EOF)
{
int num=0;
ans=0;
memset(sum,0,sizeof(sum));
memset(vis,0,sizeof(vis));
for(int i=1; i<=n; i++)
{
e[i].clear();
scanf("%d",&c[i]);
if(vis[c[i]]==0)
{
vis[c[i]]=1;
///每个颜色仅标记一次,目的是为了计算第二种不能走的路径
num++;
}
}
for(int i=1; i<n; i++)
{
int u,v;
scanf("%d%d",&u,&v);
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1,0);///求第一种不你能走得路径
ll ANS = 1LL*num*((1LL)*n*(n-1))/2;
for(int i=1; i<=n; i++) ///求第二种不能走的路径
{
if(vis[i])
{
ll ct=n-sum[i];
ans+=ct*(ct-1)/2;
}
}
printf("Case #%d: %lld\n", cas++, ANS-ans);
}
}
还没过的代码!!!,以后有时间再看看,这个思路就是直接减去子树大小。
#include <bits/stdc++.h>
using namespace std;
const int N = 4e5+100;
typedef long long ll;
int c[N],vis[N];
vector<int> e[N];
ll sum[N];///sum[i]表示的是子叶节点的颜色为 i的子树大小
ll sizes[N];///子树大小
ll bone[N];///bone[N]子树的子树大小
ll ans;
void dfs(int x,int y)
{
sizes[x]=1;
sum[c[x]]++;
//ll pre=sum[c[x]];///pre表示计算完的节点个数
for(int i=0; i<e[x].size(); i++)
{
if(e[x][i]==y) continue;
bone[c[x]]=0;
dfs(e[x][i],x);
sizes[x]+=sizes[e[x][i]];
//ll counts=sizes[e[x][i]]-(sum[c[x]]-pre);
ll counts=sizes[e[x][i]]-bone[c[x]];
///counts表示的是根节点到颜色为c[x] 的子叶节点间不同颜色的点的个数
///(sum[c[x]]-pre)为这棵子树的颜色为 c[x]的子叶节点的子树大小
ans=ans+(1LL*counts*(counts-1))/2;
sum[c[x]]+=counts;
// pre=sum[c[x]];
}
bone[c[x]]=sizes[x];
}
int main()
{
int n,cas=1;
while(scanf("%d",&n)!=EOF)
{
int num=0;
ans=0;
memset(sum,0,sizeof(sum));
memset(vis,0,sizeof(vis));
for(int i=1; i<=n; i++)
{
e[i].clear();
scanf("%d",&c[i]);
if(vis[c[i]]==0)
{
vis[c[i]]=1;
///每个颜色仅标记一次,目的是为了计算第二种不能走的路径
num++;
}
}
for(int i=1; i<n; i++)
{
int u,v;
scanf("%d%d",&u,&v);
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1,0);///求第一种不你能走得路径
ll ANS = 1LL*num*((1LL)*n*(n-1))/2;
for(int i=1; i<=n; i++) ///求第二种不能走的路径
{
if(vis[i])
{
ll ct=n-sum[i];
ans+=ct*(ct-1)/2;
}
}
printf("Case #%d: %lld\n", cas++, ANS-ans);
}
}
D. Recovering BST
题意:
给了n个点的权值。如果gcd大于1就可以相连,否则不能。问能不能构造一个二叉搜索树(左儿子都小于根节点,右儿子都大于根节点)。
题解:区间
d
p
dp
dp
d
p
[
l
]
[
r
]
[
k
]
dp[l][r][k]
dp[l][r][k]代表
[
l
,
r
]
[l,r]
[l,r]区间作为
k
k
k儿子是否可行。
参考博客
d f s dfs dfs版本
#include<bits/stdc++.h>
using namespace std;
const int maxn=800;
int G[maxn][maxn],vis[maxn][maxn][4],dp[maxn][maxn][4];
int a[maxn];
int dfs(int rt,int l,int r,int k){
int flag=0;
if(vis[l][r][k])
return dp[l][r][k];
if(l>r)///如果是等于的话还不能退出,还要看当前节点和根节点是否有边。
return 1;
for(int i=l;i<=r;i++){///在l——r区间里以i为根节点
if(G[i][rt] &&dfs(i,l,i-1,0) && dfs(i,i+1,r,1)){
flag=1;
break;
}
}
vis[l][r][k]=1;///无论根是谁,这块区间我搜过了就是搜过了
dp[l][r][k]=flag;
return flag;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
memset(G,0,sizeof(G));
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j)
continue;
if(__gcd(a[i],a[j])!=1){
G[i][j]=1;
}
}
}
/// cout<<"&&:"<<G[1][2]<<endl;
int xpp=0;
for(int i=1;i<=n;i++){
/// cout<<"i:"<<i<<" "<<"&&&&:"<<dfs(i,1,i-1,0)<<" "<<dfs(i,i+1,n,1)<<endl;
if(dfs(i,1,i-1,0)&&dfs(i,i+1,n,1))
{
printf("Yes\n");
xpp=1;
break;
}
}
if(!xpp){
printf("No\n");
}
}
return 0;
}
递推版本:
#include<bits/stdc++.h>
using namespace std;
#define maxn 700
#define rep(i,x,y) for(int i=x;i<=y;i++)
int n;
int a[maxn+5];
int g[maxn+5][maxn+5];
bool L[maxn+5][maxn+5],R[maxn+5][maxn+5];
int GCD(int x,int y) {
if(y==0) return x;
return GCD(y,x%y);
}
int main() {
scanf("%d",&n);
rep(i,1,n) {
scanf("%d",&a[i]);
}
rep(i,1,n) {
rep(j,1,n) {
g[i][j]=GCD(a[i],a[j]);
if(g[i][j]==1) g[i][j]=0;
else g[i][j]=1;
}
}
rep(i,1,n) L[i][i]=R[i][i]=1;
rep(j,1,n-1) rep(i,1,n-j) {
int k=i+j;
rep(p,i+1,k) L[i][k]|=(g[i][p]&L[p][k]&R[i+1][p]);
rep(p,i,k-1) R[i][k]|=(g[p][k]&L[p][k-1]&R[i][p]);
}
rep(i,1,n) {
if(L[i][n]&&R[1][i]) {
printf("Yes");
return 0;
}
}
printf("No");
return 0;
}
D. Zuma
题意:
给你一个长度为
n
n
n的序列,每一次可以消去一段回文串,问你最少消去多少次,能够消除整个序列。
题解:别人的题解
#include<bits/stdc++.h>
#define LL long long
#define fi first
#define se second
#define mk make_pair
#define pii pair<int,int>
#define ull unsigned long long
using namespace std;
const int N=500+7;
const int M=100+7;
const int inf=0x3f3f3f3f;
const LL INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9 + 9;
int n, a[N], f[N][N];
int dp(int i, int j) {
if(i >= j) return 1;
if(i + 1 == j) {
if(a[i] == a[j]) return 1;
else return 2;
}
if(f[i][j] != -1) return f[i][j];
f[i][j] = dp(i + 1, j) + 1;
f[i][j] = min(f[i][j],dp(i, j-1) + 1);
if(a[i] == a[j]) {
f[i][j] = min(f[i][j], dp(i + 1, j - 1));
}
for(int k = i + 1; k < j; k++) {
if(a[k] == a[i]) {
f[i][j] = min(f[i][j], dp(i + 1, k - 1) + dp(k + 1, j));
}
}
return f[i][j];
}
int main() {
memset(f, -1, sizeof(f));
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
int ans = dp(1, n);
printf("%d\n", ans);
return 0;
}