退役后的第一次补题
果然做题的时间拉长了好多
但是挺多题自己想出来了
也是蛮爽的
UPD:8.11 明早还要上课,能写多少写多少吧。
UPD:8.12 F留坑待填
有点翻车,A想了好久才弄出来
题意:
有 N 个球,第 i 个球上写着 Ai 。进行如下操作:
询问是否能在若干次操作后使得序列中存在球的值为 K
1≤N≤105 , 1≤Ai≤109 , 1≤K≤109
解答:
联系欧几里得算法,可以的得出进行若干次操作后,我们能得到的最小的数,是这些数的gcd。判断 K 是否小于 A 的最大值和差是否能被gcd整除即可。
时空复杂度: O(N)
#include
#define N 1000500
using namespace std;
inline int rd() {
int x=0,f=1;char ch=getchar();
while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
int n,k,mx,a[N];
int main() {
n = rd(), k = rd();
for (int _=1;_<=n;_++) a[_] = rd(), mx = max(mx, a[_]);
int g = a[1];
for (int _=2;_<=n;_++) g = __gcd(g, a[_]);
if (k <= mx && (mx-k)%g == 0) puts("POSSIBLE"); else puts("IMPOSSIBLE");
return 0;
}
题意:
有 N 个人参加运动会,有 M 项运动提供选择。
每个人有一个 1 ~ M 的排列,表示对着 M 项运动的喜爱排名。
作为主办方,你现在要选取一些运动,举办这些运动的比赛。
运动员会参加被举办的运动中,自己最喜欢的那一个运动参加。
你需要选出一些运动,使得举办这些运动时,参加人数最多的那一项运动参赛人数最少,输出这个人数。
1≤N≤300 ,
1≤M≤300 ,
Ai1 , Ai2 , … , AiM 是一个 1 - M 的排列。
解答:
不妨先选取所有的运动。
考虑目前参赛人数最多的运动 x ,当且仅当我们不选择该项运动,最后的答案会更优。
所以我们每次删去参赛人数最多的运动,然后对于每一个人分别统计答案。
时空复杂度: O(NM2)
#include
#define N 305
using namespace std;
inline int rd() {
int x=0,f=1;char ch=getchar();
while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
int n,m,a[N][N],vis[N],cnt[N];
int main() {
n = rd(), m = rd();
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++) a[i][j] = rd();
for (int i=1;i<=m;i++) vis[i] = 1;
int ans = n;
for (int _=1;_<=m;_++) {
memset(cnt,0,sizeof(cnt));
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (vis[ a[i][j] ]) {
cnt[ a[i][j] ]++;
break;
}
int mx = 0, id = 0;
for (int i=1;i<=m;i++)
if (cnt[i] >= mx)
mx = cnt[i], id = i;
ans = min(ans, mx);
vis[id] = 0;
}
printf("%d\n",ans);
return 0;
}
题意:
现在有 X+Y+Z 个人,第 i 个人有 Ai 金币, Bi 银币, Ci 铜币。
每个人只能给你三种硬币中的一种,你需要指定 X 个人给金币, Y 个人给银币, Z 个人给铜币。
求能得到硬币的最大值。
1≤X
1≤Y
1≤Z
X+Y+Z≤105
1≤Ai≤109
1≤Bi≤109
1≤Ci≤109
解答:
令 n=X+Y+Z
考虑 Z 等于零的情况
此时我们可以按照 Ai - Bi 排序,前 X 取 Ai 后 Y 取 Bi
当 Z 不为零的时候,相当于在排序后的序列上拿出 Z 个位置取 Ci ,剩余的前 X 个取 Ai 后 Y 个取 Bi 。
那么序列中显然存在一个或多个位置,使得在这个位置之前的所有元素选择 Ai 或 Ci ,在这个位置之后的所有元素选择 Bi 或 Ci
爆枚这个位置,用堆维护元素的差值,算出在 [1,x] 区间以及 (x,n] 区间中最优的和,更新答案即可。
时间复杂度: O(Nlog2N) ,空间复杂度: O(N)
#include
#define N 200050
using namespace std;
typedef long long LL;
inline int rd() {
int x=0,f=1;char ch=getchar();
while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
struct HbFS{int a,b,c;}Q[N];
bool cmp(HbFS p1, HbFS p2) {return p1.a-p1.b > p2.a-p2.b;}
priority_queue<int, vector<int>, greater<int> > q1,q2;
int x,y,z,n;
LL L[N],R[N];
int main() {
x = rd(), y = rd(), z = rd();
n = x + y + z;
for (int _=1;_<=n;_++)
Q[_].a = rd(), Q[_].b = rd(), Q[_].c = rd();
sort(Q+1,Q+n+1,cmp);
for (int _=1;_<=x;_++)
q1.push(Q[_].a - Q[_].c), L[_] = L[_-1] + Q[_].a;
for (int _=x+1;_<=n;_++) {
int cur = Q[_].a - Q[_].c;
L[_] = L[_-1];
if (cur > q1.top()) {
int tmp = q1.top();
q1.pop();
q1.push(cur);
L[_] -= tmp;
L[_] += Q[_].a;
} else
L[_] += Q[_].c;
}
for (int _=n;_>=n-y+1;_--)
q2.push(Q[_].b - Q[_].c), R[_] = R[_+1] + Q[_].b;
for (int _=n-y;_>=1;_--) {
int cur = Q[_].b - Q[_].c;
R[_] = R[_+1];
if (cur > q2.top()) {
int tmp = q2.top();
q2.pop();
q2.push(cur);
R[_] -= tmp;
R[_] += Q[_].b;
} else
R[_] += Q[_].c;
}
LL ans = 0LL;
for (int _=x;_<=n-y;_++) ans = max(ans, L[_]+R[_+1]);
cout << ans << endl;
return 0;
}
题意:
给定一个 N 个节点的带权树,定义图上 x , y 两点的距离是树上 x , y 两点的最短距离。求该图的最长一条哈密尔顿路径的长度。
2≤N≤105
1≤Ai<Bi≤N
给定的图是一棵树
1≤Ci≤108
读入的数都是整数
解答:
一条边最后贡献答案的次数,至多是两倍拿掉该条边后的两个连通块中较小的那个连通块大小,记这个值为 S 。
由子树大小,联想到树的重心,分两种情况。
树的重心,即树上的一个点,使得拿掉这个点之后树上最大连通块的大小 ≤⌊n2⌋ ,一棵树至多存在两个重心(且这两个重心相邻),至少存在一个重心。
综上所述,即重心一定要作为哈密尔顿路径的起点或者终点。
通过树上 dp 求解这个问题,时空复杂度: O(N) 。
#include
#define N 1000500
using namespace std;
typedef long long LL;
inline int rd() {int r;scanf("%d",&r);return r;}
struct Edge{int b,v,n;}e[2*N];
int siz[N],h[N],cnt,mark,g,n;
LL ans;
void link(int a,int b,int v) {
e[++cnt] = (Edge){b,v,h[a]}, h[a] = cnt;
}
void dfs(int u,int f,int p) {
siz[u] = 1;
int mx = 0;
for (int i=h[u];i;i=e[i].n) {
int v = e[i].b, cp = e[i].v;
if (v == f) continue;
dfs(v, u, cp);
siz[u] += siz[v];
mx = max(mx, siz[v]);
}
mx = max(mx, n-siz[u]);
if (siz[u]*2 == n) mark = p;
if (mx*2 <= n) g = u;
ans += 2LL * p * min(siz[u], n-siz[u]);
}
int main() {
n = rd();
for (int i=1;iint a = rd(), b = rd(), c = rd();
link(a, b, c);
link(b, a, c);
}
dfs(1,1,0);
if (!mark) {
mark = 2147483647;
for (int i=h[g];i;i=e[i].n)
mark = min(e[i].v, mark);
}
cout << ans - mark << endl;
return 0;
}
题意:
给定一个网格图,你需要制定一个旅行计划:
求有多少条不同的最短路。
两条路径是不同的,当且仅当路径的起点不同,选择的特殊点不同,终点不同,或者途径的点不同。答案对 109+7 取模。
1≤X1≤X2<X3≤X4<X5≤X6≤106
1≤Y1≤Y2<Y3≤Y4<Y5≤Y6≤106
解答:
这题有点恶心,真的再也不想见到这种题。
定义 Fi,j 表示网格图上从点(1,1)走到点(i,j)的不同的最短路径个数
有 Fi,j=Cii+j
利用: Fi,j=Fi−1,j+Fi,j−1
可以得出: ∑ix=1∑jy=1Fx,y=Fi+1,j+1−1
类比二维前缀和,可以将起点的区域转化成四个带权的单点
+1 : (x2+1,y2+1), (x1,y1)
-1 : (x1,y2+1), (x2+1,y1)
同理,也可以将终点的区域转化成四个带权的单点
原问题可以转化成求16个固定起点和终点,在固定区域 [x3,x4][y3,y4] 中选择一个中点,不同的最短路是多少
考虑这条路径进入固定区域的点以及离开固定区域的点,原问题路径按照进入固定区域的点和离开固定区域的点分类,最后答案乘上在固定区域内行走的路径长度,即为所求。
时空复杂度为 O(106) ,来源于组合数的预处理
#include
#define mod 1000000007
#define N 2000500
using namespace std;
typedef long long LL;
LL Fac[N],Inv[N],Iac[N];
int x[7],y[7];
inline int rd() {
int x=0,f=1;char ch=getchar();
while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
inline void inc(LL &x,LL y) {x=(x+y)%mod;}
inline LL C(int x,int y) {
return 1LL * Fac[x+y] * Iac[x] % mod * Iac[y] % mod;
}
inline LL W(int x1,int y1,int x2,int y2) {
x1 = abs(x1); y1=abs(y1); x2=abs(x2); y2=abs(y2);
return (1LL *C(x2+1,y2+1) - C(x2+1,y1) - C(x1,y2+1) + C(x1,y1)+mod + mod) % mod;
}
void init() {
for (int i=1;i<=6;i++) x[i] = rd();
for (int i=1;i<=6;i++) y[i] = rd();
}
void prepare() {
int n = 2000000;
Fac[0] = Fac[1] = 1;
for (int i=2;i<=n;i++) Fac[i] = 1LL * Fac[i-1] * i % mod;
Inv[0] = Inv[1] = 1;
for (int i=2;i<=n;i++) Inv[i] = 1LL * (mod - mod / i) * Inv[mod%i] % mod;
Iac[0] = Iac[1] = 1;
for (int i=2;i<=n;i++) Iac[i] = 1LL * Iac[i-1] * Inv[i] % mod;
}
void solve() {
LL ans = 0LL;
for (int _=x[3];_<=x[4];_++)
inc(ans, 1LL*(mod-_-y[3])*W(_-x[2],y[3]-1-y[2],_-x[1],y[3]-1-y[1])%mod * W(_-x[5],y[3]-y[5],_-x[6],y[3]-y[6])%mod);
for (int _=x[3];_<=x[4]; _++)
inc(ans, 1LL*(_+y[4]+1)*W(_-x[2],y[4]-y[2],_-x[1],y[4]-y[1])%mod*W(_-x[5],y[4]-y[5]+1,_-x[6],y[4]-y[6]+1)%mod);
for (int _=y[3];_<=y[4];_++)
inc(ans, 1LL*(mod-_-x[3])*W(x[3]-1-x[2],_-y[2],x[3]-1-x[1],_-y[1])%mod *W(x[3]-x[5], _-y[5], x[3]-x[6], _-y[6])%mod);
for (int _=y[3];_<=y[4];_++)
inc(ans, 1LL*(_+x[4]+1)*W(x[4]-x[2],_-y[2],x[4]-x[1],_-y[1])%mod *W(x[4]-x[5]+1,_-y[5],x[4]-x[6]+1,_-y[6])%mod);
cout<int main() {
init();
prepare();
solve();
return 0;
}