B - Groundhog and Apple Tree
题意: 给你一个棵以1为根的树,每条边有障碍物,经过时会减少一定的hp,如果吃了第i个苹果,则会回复
a
i
a_i
ai,当没有苹果的时候,可以在原地休息一个单位时间,回复1hp,hp不能为负,但可以是0到正无穷大。一个苹果只能吃一次,但每次经过一条边时,都会消耗hp。边很脆弱,每条边只能最多经过2次。从根节点1开始出发,初始hp为0,要求经过所有点后返回1号点,求最少休息时间。
思路: 首先直觉告诉我们肯定要在起点储存足够的hp值,再开始行动是最优的。然后我们每次走每个点的子树顺序会影响到结果,所以我们需要决定如何走才是最优的
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<stack>
#include<queue>
#define INF 0x3f3f3f3f
#define lowbit(x) x & -x
#define lson root<<1,l,mid
#define rson root<<1|1,mid+1,r
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e5+5;
vector<pair<int,int> >G[N];
ll dp[N],sum[N];
int a[N];
struct node{
ll in,out;
};
bool cmp1(node q,node qq){
if(q.in!=qq.in)
return q.in<qq.in;
return q.out>qq.out;
}
bool cmp2(node q,node qq){
if(q.out!=qq.out)
return q.out>qq.out;
return q.in<qq.in;
}
void dfs(int u,int fa){
vector<node>tmp1,tmp2;
sum[u]=a[u];
for(auto v:G[u]){
if(v.first==fa)
continue;
dfs(v.first,u);
sum[u]+=sum[v.first]-2ll*v.second;
node tmp;
if(dp[v.first]+sum[v.first]>=v.second){
tmp.in=v.second+dp[v.first];
tmp.out=sum[v.first]+dp[v.first]-v.second;
}else{
tmp.out=0;
tmp.in=2ll*v.second-sum[v.first];
}
if(tmp.in<=tmp.out)
tmp1.push_back(tmp);
else
tmp2.push_back(tmp);
}
sort(tmp1.begin(),tmp1.end(),cmp1);
sort(tmp2.begin(),tmp2.end(),cmp2);
ll cur = a[u];
for(auto t : tmp1){
if(cur < t.in){
dp[u] += t.in - cur;
cur = t.in;
}
cur = cur - t.in + t.out;
}
for(auto t : tmp2){
if(cur < t.in){
dp[u] += t.in - cur;
cur = t.in;
}
cur = cur - t.in + t.out;
}
}
int main(){
#ifdef Mizp
freopen("in.txt","r",stdin);
#endif
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
for(int i=0;i<=n;i++){
G[i].clear();
dp[i]=sum[i]=0;
}
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<n;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
G[u].push_back({v,w});
G[v].push_back({u,w});
}
dfs(1,0);
printf("%lld\n",dp[1]);
}
return 0;
}
E - Groundhog Chasing Death
题意: 给你a,b,c,d,x,y,求
∏
i
=
a
b
∏
j
=
c
d
g
c
d
(
x
i
,
y
j
)
\prod_{i=a}^{b}\prod_{j=c}^{d}{gcd(x^i,y^j)}
i=a∏bj=c∏dgcd(xi,yj)
思路: 他人详细题
在比赛的时候,由于听不懂队友的做法,所以直接丢给队友做了,分别对x,y进行质因数分解之后,算出x,y的公共质因子的贡献,由于比赛时队友没有意识到底数相同,将每个质因数的贡献次数相加再进行欧拉降幂,最后再用快速幂求出真正的贡献
所以在需要进行许多次快速幂的时候,我们要意识到是否能进行递推,或者将底数的相同的所有次幂相加后进行欧拉降幂,然后变成每个底数只需要算一次快速幂,可以降低复杂度
#include <bits/stdc++.h>
#define lowbit(i) ((i)&(-i))
#define ls (p << 1)
#define rs (p << 1 | 1)
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
typedef pair <int, int> pii;
typedef pair <ll, ll> pll;
const double pi = acos(-1.0);
const int INF = 0x3f3f3f3f;
const int N = 1e6;
const ll M = 998244353;
bool vis[N + 15];
ll prime[N + 15], xp[105], yp[105], cntx[105], cnty[105];
int pcnt = 0;
void Euler(){
vis[0] = vis[1] = 1;
for(int i = 2; i <= N; i++){
if(!vis[i]) prime[pcnt++] = i;
for(int j = 0; 1ll * i * prime[j] <= N; j++){
vis[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
}
ll quick_pow(ll a, ll b){
ll res = 1;
while(b > 0){
if(b & 1) res = (res * a) % M;
a = (a * a) % M;
b >>= 1;
}
return res;
}
int main() {
Euler();
int a, b, c, d, x, y;
scanf("%d%d%d%d%d%d", &a,&b,&c,&d,&x,&y);
if(a > b) swap(a, b);
if(c > d) swap(c, d);
int numx = 0, numy = 0;
// 对x质因数分解
ll tmpx = x;
for(int i = 0; i < pcnt && tmpx / prime[i] >= prime[i]; i++) {
if(tmpx % prime[i] == 0) {
xp[++numx] = prime[i];
while(tmpx % prime[i] == 0) {
tmpx /= prime[i];
cntx[numx]++;
}
}
}
if(tmpx != 1 && tmpx != 0) {
xp[++numx] = tmpx;
cntx[numx] = 1;
}
// 对y质因数分解
ll tmpy = y;
for(int i = 0; i < pcnt && tmpy / prime[i] >= prime[i]; i++) {
if(tmpy % prime[i] == 0) {
yp[++numy] = prime[i];
while(tmpy % prime[i] == 0) {
tmpy /= prime[i];
cnty[numy]++;
}
}
}
if(tmpy != 1 && tmpy != 0) {
yp[++numy] = tmpy;
cnty[numy] = 1;
}
ll res = 1;
for(int i = 1; i <= numx; i++) {
for(int j = 1; j <= numy; j++) {
if(xp[i] != yp[j]) continue;
ll sum = 0;
for(ll nx = a; nx <= b; nx++) {
ll ny = (nx * cntx[i] + cnty[j] - 1) / cnty[j];
if(ny <= c) {
sum = (sum + nx * cntx[i] * (d - c + 1)) % (M - 1);
}
else if(ny > d) {
ll num = 1ll * (c + d) * (d - c + 1) / 2;
sum = (sum + cnty[j] * num) % (M - 1);
}
else {
ll num = (c + ny - 1) * (ny - c) / 2;
sum = (sum + cnty[j] * num) % (M - 1);
sum = (sum + nx * cntx[i] * (d - ny + 1)) % (M - 1);
}
}
// printf("%lld %lld\n", xp[i], sum);
res = (res * quick_pow(xp[i], sum)) % M;
}
}
printf("%lld\n", res);
return 0;
}
J - The Escape Plan of Groundhog
题意: 给你一个n*m的01矩阵(1<=n,m<=500),求存在多少个子矩阵,使得子矩阵边界都为1,且内部的0和1的数目相差不超过1(不含四周边界)。
思路: 由于数据范围,我们可以想一个O(n^3)的思路。
我们可以预处理每一列的1的个数,前缀和即可。
然后进行枚举O(n^2)枚举子矩阵上下边界,然后对O(n)遍历每行中符合的位置即可,我们能快判断出边界是否全为1,但是我们如何内部0与1的个数之差呢?
我们可以开个桶,存下每次确定一个边界全为1时,就统计一下桶里有没有符合的边界与该边界组成新的子矩阵,然后枚举上下行的边界不为1时,记得清空即可
需要注意的是因为0与1的个数有可能为负数,我们可能会用map或unordermap来当作桶,这样就多了O(log)的复杂度,且常数比较大,就会TLE,我们可以开个数组当桶即可,每次右移一个较大的整数,使得不会出现负数的情况即可
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<stack>
#include<queue>
#define INF 0x3f3f3f3f
#define lowbit(x) x & -x
#define lson root<<1,l,mid
#define rson root<<1|1,mid+1,r
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e6+5;
int sum[505][505],a[505][505];
int num[N];
vector<int>tmp;
void cl(){
for(int it: tmp){
num[it]--;
}
tmp.clear();
}
int main(){
#ifdef Mizp
freopen("in.txt","r",stdin);
#endif
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
sum[i][j]=sum[i-1][j];
if(a[i][j]==1)
sum[i][j]+=1;
}
}
ll ans=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
int cnt=3e5;
for(int k=1;k<=m;k++){
if(!a[i][k]||!a[j][k]){
cnt=3e5;
cl();
}else if(sum[j-1][k]-sum[i][k]==j-i-1){
ans+=num[cnt]+num[cnt-1]+num[cnt+1];
cnt+=2*(sum[j-1][k]-sum[i][k])-(j-i-1);
num[cnt]++;
tmp.push_back(cnt);
}else{
cnt+=2*(sum[j-1][k]-sum[i][k])-(j-i-1);
}
}
cl();
}
}
printf("%lld\n",ans);
return 0;
}