Tree 重构树 DP
Distance 组合数学,推式子,拆贡献
idol!! 数学题
Sequence 前缀和,结论
Gcd 分类讨论,细节
对边权进行排序,每次从小到大加入该边的左右集合。dp[i][j]代表 一个集合i个点里面,选了j个白点的最大收益。又因为当前边权一定是最大值,故左右集合内部贡献我们已经算出,只需要再在合并的时候,算上这条边新来的贡献即可。直接进行dp的话是一个n^3的,进行一个启发式合并,可将一个n缩短为log
#include
using namespace std;
typedef long long int ll;
ll dp[3030][3030],f[3030],sizeson[3030];
ll temp[3030];
ll inf=1e18;
int getf(int x)
{
if(x==f[x])
return f[x];
else
{
f[x]=getf(f[x]);
return f[x];
}
}
struct node
{
int b,e;
ll w;
};
struct node s[3030];
int col[3030];
ll cost[3030];
bool cmp(struct node x,struct node y)
{
return x.w>n;
for(int i=1; i<=n; i++)
{
f[i]=i;
sizeson[i]=1;
}
for(int i=1; i<=n; i++)
{
cin>>col[i];
}
for(int i=1; i<=n; i++)
{
cin>>cost[i];
}
for(int i=1; i>s[i].b>>s[i].e>>s[i].w;
}
sort(s+1,s+n,cmp);
for(int i=1; i<=n; i++)
{
if(col[i]==0)
{
dp[i][1]=0;
dp[i][0]=-cost[i];
}
else
{
dp[i][1]=-cost[i];
dp[i][0]=0;
}
}
int root=0;
for(int i=1; i=0; j--)
{
for(int rr=0; rr<=min(j,sizer); rr++)
{
ll LL=j-rr;
ll leftbai=LL;
ll lefthei=sizel-LL;
ll righbai=rr;
ll righhei=sizer-rr;
if(LL<=sizel)
temp[j]=max(temp[j],dp[l][LL]+dp[r][rr]+(leftbai*righhei+lefthei*righbai)*s[i].w );
}
}
sizeson[l]+=sizeson[r];
f[r]=l;
root=l;
for(int j=0; j<=sizer+sizel; j++)
{
dp[l][j]=temp[j];
}
}
root=getf(n);
ll ans=-1e18;
for(int i=0; i<=sizeson[root]; i++)
{
ans=max(ans,dp[root][i]);
}
cout<
考虑算贡献,a[i]和b[j]进行匹配时,贡献为其差的绝对值*合法集合数。枚举ij共同排名k,u之前选k个,u1也选k个,再枚举v,v1之后选几个。这样是一个n^3的,但是我们对u,u1,共同选k这一函数打表发现,这是一个完美的可以递推的函数。故找到规律后直接递推预处理,省去一维。
#include
using namespace std;
typedef long long ll;
const int N = 4e3 + 7;
ll c[N][N];
ll a[N], b[N];
ll mp[N][N];
const ll mod = 998244353;
int main()
{
for (int i = 0; i < N; i++)
{
c[0][i] = c[i][0] = 1;
}
for (int i = 1; i < N; i++)
{
for (int j = 1; j < N; j++)
{
c[i][j] = (c[i - 1][j] + c[i][j - 1]) % mod;
}
}
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
}
for (int i = 1; i <= n; i++)
{
scanf("%lld", &b[i]);
}
ll res = 0;
sort(a + 1, a + n + 1);
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (a[i] == b[j])
{
continue;
}
// res += (((abs(a[i] - b[j]) * get(i - 1, j - 1)) % mod) * get(n - i, n - j)) % mod;
int x = i - 1, y = j - 1, u = n - i, v = n - j;
res += (((abs(a[i] - b[j]) * c[i - 1][j - 1] % mod) * c[n - i][n - j] % mod)) % mod;
res %= mod;
}
}
printf("%lld\n", res);
}
找规律,推式子,开int128数学题。
做一个前缀和,对于前缀和的奇偶性再做前缀和。当分成k段每一段都是2的倍数时,首先[L,R]的前缀和必须也是2的倍数,即R和L-1的前缀和奇偶性相同。从R开始,一旦有一个跟其前缀和奇偶性相同的L,就分一段,直到L-1,故只需要看L-1到R-1有多少和R奇偶性相同的前缀和,要求有k-1个就行。
#include
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
ll c[N], q[N];
ll v[N][3];
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int n, qq;
scanf("%d%d", &n, &qq);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &q[i]);
c[i] = c[i - 1] + q[i];
v[i][c[i] % 2] = v[i - 1][c[i] % 2] + 1;
v[i][!(c[i] % 2)] = v[i - 1][!(c[i] % 2)];
}
while (qq--)
{
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
if ((c[r] % 2) != (c[l - 1] % 2))
{
puts("NO");
}
else
{
int op = c[r] % 2;
int cnt = v[r - 1][op] - v[l - 1][op];
if (cnt < k - 1)
{
puts("NO");
}
else
{
puts("YES");
}
}
}
}
}
分类讨论即可。