A Don’t Starve
题意:
给定一个二维平面,从原点(0, 0)出发,地图中存在N个点,每个点坐标保证不同,每个点上都有1单位物品,每次离开当前点,物品会重新刷新。要求每次移动距离严格递减,求最多能拿到多少物品。
1
<
=
N
<
=
2000
,
−
20000
<
=
x
[
i
]
,
y
[
i
]
<
=
20000
1 <= N <= 2000, -20000 <= x[i], y[i] <= 20000
1<=N<=2000,−20000<=x[i],y[i]<=20000
思路:
由于每个点可重复到达。最开始一直以为类似Link with Level Editor I分层图进行dp转移。但是后来发现,不像。
一直以为是BFS。一共才2000 * 2000条边,且以为最多拿2000次。。没想到MLE。。(答案太大了)
注意到每次移动距离要严格递减。那么可对所有路线距离进行从大到小排序。可满足拓扑结构。
排序后,两点距离较大的点会去更新两点距离较小的点。(可尝试画图)
那么每次需要处理长度相同的段进行dp转移。(路径距离严格递减)
f[i][j]:从i号点走到j号点的拿到物品的集合。
属性:max
S[i]:走到i号点所拿物品的最大值。
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , S [ i ] + 1 ) f[i][j] = max(f[i][j], S[i] + 1) f[i][j]=max(f[i][j],S[i]+1)
那么总的时间复杂度O(n ^ 2 logn)
code:
ll x[maxn], y[maxn], f[maxn][maxn], s[maxn];
struct note{
int u;
int v;
ll w;
bool operator <(const note &a) const{
return w > a.w;
}
};
int main()
{
int N;
scanf("%d", &N);
bool flog = false;
vector <PII> alls;
alls.push_back({0, 0});
for(int i = 1 ; i <= N ; i ++)
{
scanf("%lld %lld", &x[i], &y[i]);
if(x[i] == 0 && y[i] == 0) flog = true;
else
{
alls.push_back({x[i], y[i]});
}
}
for(int i = 0 ; i < alls.size() ; i ++)
{
s[i] = -2e18; //多少点
for(int j = 0; j < alls.size() ; j ++)
f[i][j] = -2e18;
}
if(flog) f[0][0] = 1, s[0] = 1;
else f[0][0] = 0, s[0] = 0;
vector <note> edge;
for(int i = 0 ; i < alls.size() ; i ++)
{
for(int j = 1 ; j < alls.size() ; j ++)
{
if(i != j)
{
edge.push_back({i,j, (alls[i].first - alls[j].first) * (alls[i].first - alls[j].first) + (alls[i].second - alls[j].second) * (alls[i].second - alls[j].second)});
}
}
}
sort(edge.begin(), edge.end());
ll res = 0;
for(int i = 0 ; i < edge.size() ; i ++)
{
int j = i;
ll w = edge[i].w;
vector <int> temp;
while(j < edge.size() && edge[j].w == w) temp.push_back(j), j ++;
for(auto t : temp)
{
f[edge[t].u][edge[t].v] = max(f[edge[t].u][edge[t].v], s[edge[t].u] + 1);
res = max(res, f[edge[t].u][edge[t].v]);
}
for(auto t : temp)
s[edge[t].v] = max(s[edge[t].v], f[edge[t].u][edge[t].v]);
i = j - 1;
}
printf("%lld\n",res);
return 0;
}
D Birds in the tree
题意:
给定一棵树,n个节点。每个节点都有一个权值0 / 1,求图中存在多少连通点集中满足叶子节点(度数为1)权值相同。对结果 % 1e9 + 7。
$ 1 <= n <= 3e5$
思路:
个人感觉树上取模计算方案数,多半为DP。
发现度数为1的点,有可能是叶子节点,也有可能是根节点。
因为之前根节点没考虑,导致样例老是多算。。
发现可能存在节点本身权值为0,但是连通点集中度数为1的点权值为1。
也会存在节点本身权值为1,连通点集中度数为1的点权值为1。
f[i][0 / 1]:以i号点为根的子树中,连通点集且叶子节点为0 / 1的集合
属性:max
f [ i ] [ 0 ] = ∏ s o n ( f [ s o n ] + 1 ) f[i][0] = \prod \limits_{son} (f[son] + 1) f[i][0]=son∏(f[son]+1)
code:
har str[maxn];
int h[maxn], ne[maxn * 2], e[maxn * 2], idx;
ll f[maxn][3];
const ll mod = 1e9 + 7;
ll ans;
ll get_mod1(ll a, ll b)
{
return (a + b) % mod;
}
ll get_mod2(ll a, ll b)
{
return (a % mod * (b % mod)) % mod;
}
void add(int u, int v)
{
e[idx] = v;
ne[idx] = h[u];
h[u] = idx ++;
}
void dfs(int sta, int fa)
{
f[sta][0] = f[sta][1] = 1;
ll sum1 = 0, sum0 = 0; //子节点
for(int i = h[sta] ; i != -1 ; i = ne[i])
{
int son = e[i];
if(son == fa) continue;
dfs(son, sta);
f[sta][1] = get_mod2(f[sta][1], f[son][1] + 1); //选son节点 + 不选
sum1 = get_mod1(sum1, f[son][1]);
f[sta][0] = get_mod2(f[sta][0], f[son][0] + 1);
sum0 = get_mod1(sum0, f[son][0]);
}
if(str[sta] == '1')
{
f[sta][0] = (f[sta][0] - 1 + mod) % mod;
ans = get_mod1(ans, get_mod1(f[sta][1], (f[sta][0] - sum0 + mod) % mod));
}
else
{
f[sta][1] = (f[sta][1] - 1 + mod) % mod; // - 孤立点 - 选一个节点的方案
ans = get_mod1(ans, get_mod1(f[sta][0], (f[sta][1] - sum1 + mod) % mod));
}
}
int main()
{
int n;
scanf("%d", &n);
scanf("%s", str + 1);
memset(h, -1, sizeof(h)), idx = 0;
for(int i = 1 ; i < n ; i ++)
{
int u, v;
scanf("%d %d", &u, &v);
add(u, v), add(v, u);
}
dfs(1, -1);
// for(int i = 1 ; i <= n ; i ++)
// printf("%d %lld %lld\n", i, f[i][0], f[i][1]);
printf("%lld\n", ans);
return 0;
}