A
题意
给定平面上 n n n 个点,从原点出发直线前往这些点收集食物,收集完再前往下一个点。
每当离开一个有食物的点后该点食物刷新,每次移动距离严格下降。
问最多收集到多少食物。 n ≤ 2 × 1 0 3 n≤2×10^3 n≤2×103
思路
每两个点之间建一条边,距离作为边权,按照边权从大到小排序。
f [ i ] f[i] f[i] 作为到达点 i i i 能收集到的最大食物数量。
对于边 ( u , v ) (u,v) (u,v),转移方程 f [ v ] = m a x ( f [ v ] , f [ u ] + 1 ) f[v] = max(f[v],f[u]+1) f[v]=max(f[v],f[u]+1),
由于距离要严格下降,因此对于距离相等的边要同时处理。
复杂度 O ( n 2 l o g n 2 + n 2 ) O(n^2logn^2+n^2) O(n2logn2+n2)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int,int> PII;
typedef long long ll;
const int N = 2010, INF = 0x3f3f3f3f;
int n;
int f[N], g[N];
struct Edge
{
int u;
int v;
int d;
bool operator<(const Edge &a)
{
return d > a.d;
}
};
int x[N], y[N];
void solve()
{
cin >> n;
for(int i = 1; i <= n; i++) cin >> x[i] >> y[i];
vector<Edge> edge;
for(int i = 0; i <= n; i++)
for(int j = 1; j <= n; j++)
if(i != j) edge.push_back({i, j, (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j])});
sort(edge.begin(), edge.end());
for(int i = 1; i <= n; i++) f[i] = -INF, g[i] = -INF;
int m = edge.size();
f[0] = 0;
for(int i = 0; i < m;)
{
vector<Edge> tmp;
int j = i;
while(j < m && edge[j].d == edge[i].d)
{
tmp.push_back(edge[j]);
j++;
}
for(auto t : tmp) g[t.v] = -INF;
for(auto t : tmp) g[t.v] = max(g[t.v], f[t.u] + 1);
for(auto t : tmp) f[t.v] = max(f[t.v], g[t.v]);
i = j;
}
int res = 0;
for(int i = 1; i <= n; i++) res = max(res, f[i]);
cout << res << endl;
return;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) solve();
return 0;
}
D
题意
给定 n n n 个节点的树,树上每个点为 0 , 1 0,1 0,1 两种颜色之一。
问树上连通块满足该连通块构成的生成子图中度为 1 1 1 节点 均同色 的方案数。 n ≤ 3 × 1 0 5 n≤3×10^5 n≤3×105
思路
均为 0 0 0 和均为 1 1 1 的两种情况解决方法一样,跑两遍即可。
考虑度为 1 1 1 的节点颜色为 f l a g flag flag 。
设 d p [ i ] dp[i] dp[i] 为以编号 i i i 的节点为根的子树的方案数。
对于根 u u u 及其子节点 v i v_i vi ,每个子节点 v i v_i vi 都有 d p [ v i ] dp[v_i] dp[vi] 种方案,再加上不选 v i v_i vi 这个子节点这种情况,即为 d p [ v i ] + 1 dp[v_i]+1 dp[vi]+1
对于所有子节点 v i v_i vi ,都可以独立的选和不选,根据乘法原理,得到选择子节点 v i v_i vi 的所有方案数为 ∏ ( d p [ v i ] + 1 ) ∏(dp[v_i]+1) ∏(dp[vi]+1)
还有一种所有子节点都不选,只留下根,但必须满足根为 f l a g flag flag ,
则有 d p [ u ] = ∏ ( d p [ v i ] + 1 ) − 1 + ( a [ u ] = = f l a g ) dp[u]=∏(dp[v_i]+1)-1+(a[u]==flag) dp[u]=∏(dp[vi]+1)−1+(a[u]==flag)
记录答案时,
若 a [ u ] = f l a g a[u]=flag a[u]=flag,直接 r e s + = d p [ u ] res+=dp[u] res+=dp[u]
若 a [ u ] ≠ f l a g a[u]≠flag a[u]=flag,需要减去 u u u 和单独一个子节点 v i v_i vi 形成的树,此时根节点 u u u 的度为1,根据加法原理 r e s + = d p [ u ] − ∑ d p [ v i ] res+=dp[u]-\sum dp[v_i] res+=dp[u]−∑dp[vi]
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int,int> PII;
typedef long long ll;
const int N = 300010, INF = 0x3f3f3f3f, mod = 1e9 + 7;
int n;
int dp[N];
vector<int> g[N];
int res;
bool a[N];
bool flag;
void dfs(int u, int fa)
{
int sum = 0, mul = 1;
for(auto v : g[u])
{
if(v == fa) continue;
dfs(v, u);
mul = mul * (dp[v] + 1) % mod;
sum = (sum + dp[v]) % mod;
}
dp[u] = mul - 1 + (a[u] == flag);
if(a[u] == flag) res = (res + dp[u]) % mod;
else res = (res + dp[u] - sum + mod) % mod;
}
void solve()
{
cin >> n;
string s;
cin >> s;
for(int i = 0; i < s.length(); i++)
if(s[i] == '1') a[i + 1] = true;
for(int i = 1; i <= n - 1; i++)
{
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
flag = 0;
dfs(1, 0);
flag = 1;
dfs(1, 0);
cout << res << endl;
return;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) solve();
return 0;
}