目录
1009-关于 agKc 实在不喜欢自动化于是啥都自己合成这件事
1004-战争游戏
首先,如果树的直径小于等于人r1*2+1 ,那么进攻方只需选择树的直 径的中点,便可一回合就把防守方炸死。
否则,若 r1*2>=r2 ,那么进攻方总可以用下面的方法把防守方逼死:
首先,进攻方选择一个与防守方的一开始所在位置距离为 r1的点作为轰炸中心,之后每回合,将轰炸中心往防 守方当前所在位置移动 1步,直到防守方移动至叶子节点,那么这一回合进攻方就能把防守方炸死。由于防守方怎么移动都不能经过轰炸中心所在的点(不然一定会被炸死),所以这种策略一定可以在一定回合内把防守方炸死。
若 r1*2<r2 ,那么无论防守方在什么地方,进攻方选择什么位置作 为轰炸中心,树上总会存在防守方可以到达的没有被轰炸的位置,此时防 守方可以获胜。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
const int mod=998244353;
vector<int>to[N];
int ans = 0;
int dfs(int x,int fa) {
int d1=0,d2=0;
for(auto ed : to[x]) {
int y=ed, z=1;
if(y==fa) continue;
int d=dfs(y,x)+z;
if(d>=d1) d2=d1,d1=d;
else if(d>d2) d2=d;
}
ans=max(ans,d1+d2);
return d1;
}
void solve() {
int n,s,r1,r2;
cin>>n>>s>>r1>>r2;
for(int i=1; i<n; i++) {
int u,v;
cin>>u>>v;
to[u].push_back(v);
to[v].push_back(u);
}
dfs(s,-1);
if(ans<=2*r1||r2<=2*r1) cout<<"Kangaroo_Splay"<<'\n';
else cout<<"General_Kangaroo"<<'\n';
for(int i=1;i<=n;i++){
to[i].clear();
}
ans=0;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}
1007-创作乐曲
首先,题目可以转化为询问子乐曲内最多可以保留多少个音符,使这
些保留的音符构成的乐曲是“美妙动听的”。考虑这样一个性质:对于一个音高为x的音符,在最优解中,接在其
后面的音符至多有两种可能,分别为离这个音符最近的音高在 x到x+k内的音符和离这个音符最近的音高在x-k到x内的音符。
所以我们只需要从一个方向遍历的同时,维护一棵权值线段树,节点表示这一部分值所在位置的最值,如果是从后往前进行枚举的,那节点表示这一部分值所在位置的最小。
然后用一个简单的dp递推答案。
#include "bits/stdc++.h"
using namespace std;
#define int long long
const int N = 1e5 + 100;
const int M = 1e7+100;
int n, m, k;
int cnt;
int a[N];
int ls[M];
int rs[M];
int vis[M];
void update(int p, int l, int r, int w, int id) {
vis[p] = id;
if (l == r)return;
int mid = (l + r) / 2;
if (w <= mid) {
if (!ls[p])ls[p] = ++cnt;
update(ls[p], l, mid, w, id);
} else {
if (!rs[p])rs[p] = ++cnt;
update(rs[p], mid + 1, r, w, id);
}
}
int query(int p, int l, int r, int x, int y) {
if (l >= x && r <= y)return vis[p];
int mid = (l + r) >> 1;
int ans = n + 2;
if (x <= mid) {
ans = min(ans, query(ls[p], l, mid, x, y));
}
if (y >= mid + 1) {
ans = min(ans, query(rs[p], mid + 1, r, x, y));
}
return ans;
}
int nxt1[N], nxt2[N];
int dp[N];
void solve() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i++)cin >> a[i];
vis[0] = n + 1;
vis[1] = n + 1;
cnt=1;
for (int i = n; i >= 1; i--) {
nxt1[i] = query(1, 1, m, a[i], min(m, a[i] + k));
nxt2[i] = query(1, 1, m, max(1ll, a[i] - k), a[i]);
update(1, 1, m, a[i], i);
}
int q;
cin >> q;
while (q--) {
int l,r;
cin>>l>>r;
for(int i=l;i<=r;i++)dp[i]=0;
int ans=0;
for(int i=r;i>=l;i--){
dp[i]=1;
if(nxt1[i]<=r)dp[i]=max(dp[i],dp[nxt1[i]]+1);
if(nxt2[i]<=r)dp[i]=max(dp[i],dp[nxt2[i]]+1);
ans=max(ans,dp[i]);
}
cout<<r-l+1-ans<<'\n';
}
for(int i=0;i<=cnt;i++){
ls[i]=0;
rs[i]=0;
vis[i]=0;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while (T--)solve();
return 0;
}
1008-循环图
方法一(赛时过了,赛后T了,杭电oj是这样的):
对于第一组样例进行解释
3 4 5 6
1 2 1
1 3 1
3 4 1
2 5 1
初始有的路径为
1->2
1->3
3->4
2->5
加一次后可增加的路劲为
4->5
4->6
6->7
5->8
因为v是小于2n的,我们以每2n个数作为一组,即1~2n,2n+1~4n……
可以发现主要有两种转移方式,
1、在这2n个之内自己转移的,比如1->2,4->5,7->8
2、在相邻的之间进行转移的,比如6->7
可以明显的发现,转移的方式是相同的,比如1->2,7->8,对2n取个模,其实都是1->2(废话,就是这么得到的),所以可以通过矩阵乘法的方式来模拟这个过程。
首先对于2n之内的数,有多少种情况可以转移到它,用一个简单的递推是好转移的,先把这个求下来。赋值成一个矩阵a
a1 |
a2 |
a3 |
a4 |
a5 |
a6 |
a7(总的) |
前2n行表示能达到这个数的方案数,第2n+1行表示之前的方案数之和,如果a1~a8代表7~12那么第7位代表前6个数的方案数的总和,这个矩阵的初始值是,a1~a8代表1~6,最后一位为0
我们设一张2n*2n矩阵,设为b,来模拟2n个可能的转移,拿样例来说明(这里的1到6都是取模之后的)
1->2
1->3
3->4
2->5
4->5
4->6
6->7(相当于6->1)
5->8(相当于5->2)
1可以通过上一次的6得到所以
1 | 2 | 3 | 4 | 5 | 6 | |
1 | 1 | |||||
2 | ||||||
3 | ||||||
4 | ||||||
5 | ||||||
6 |
2通过1得到,所以把第1行累加到第2行上
2通过上一次的5得到
1 | 2 | 3 | 4 | 5 | 6 | |
1 | 1 | |||||
2 | 1 | 1 | ||||
3 | ||||||
4 | ||||||
5 | ||||||
6 |
3通过1得到,所以把第1行累加到第3行上
1 | 2 | 3 | 4 | 5 | 6 | |
1 | 1 | |||||
2 | 1 | 1 | ||||
3 | 1 | |||||
4 | ||||||
5 | ||||||
6 |
4通过3得到,所以把第3行累加到第4行上
1 | 2 | 3 | 4 | 5 | 6 | |
1 | 1 | |||||
2 | 1 | 1 | ||||
3 | 1 | |||||
4 | 1 | |||||
5 | ||||||
6 |
5通过2得到,所以把第2行累加到第5行上
5通过4得到,所以把第4行累加到第5行上
1 | 2 | 3 | 4 | 5 | 6 | |
1 | 1 | |||||
2 | 1 | 1 | ||||
3 | 1 | |||||
4 | 1 | |||||
5 | 1 | 2 | ||||
6 |
6通过4得到,所以把第4行累加到第6行上
1 | 2 | 3 | 4 | 5 | 6 | |
1 | 1 | |||||
2 | 1 | 1 | ||||
3 | 1 | |||||
4 | 1 | |||||
5 | 1 | 2 | ||||
6 | 1 |
(样例每条边的w都是1,实际操作要乘w)
把b和a两个矩阵相乘,可以发现得到的矩阵其实就是下2n个数的方案数,a1~a6就变成了a7~a12
题目要求的实际是L~R的方案数之和,只要求出前缀和相减就行,
我们改变一下b矩阵,初始值就变成l
a1 | a2 | a3 | a4 | a5 | a6 | ||
a1 | 1 | ||||||
a2 | 1 | 1 | |||||
a3 | 1 | ||||||
a4 | 1 | ||||||
a5 | 1 | 2 | |||||
a6 | 1 | ||||||
1 | 1 | 1 | 1 | 1 | 1 | 1 |
在最底下加一行,用来算a矩阵中第2n+1个数表示的前缀和。(因为矩阵乘法,b矩阵的第2n+1行*a矩阵的第1列,初值设成这样就能求前缀和)
可以快速的求出L,R,分别对应着第几对2n个数,然后用矩阵快速幂快速求出那一对对应的a矩阵,第2n+1个表示前个前缀和,用前a矩阵的2n项来补足
到L 的剩余的部分,R同理,相减即为答案
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 210;
const int M = 1e4 + 500;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
//ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int n, m, l, r;
struct jz {
int a[N][N];
jz() {
memset(a, 0, sizeof(a));
}
inline void clear() {
memset(a, 0, sizeof(a));
}
inline void build_E() {
for (int i = 1; i <= n * 2 + 1; i++) a[i][i] = 1;
}
};
jz operator*(const jz &x, const jz &y) {
jz res;
for (int k = 1; k <= n * 2 + 1; k++) {
for (int i = 1; i <= n * 2 + 1; i++) {
for (int j = 1; j <= n * 2 + 1; j++) {
res.a[i][j] = (res.a[i][j] + x.a[i][k] * y.a[k][j] % mod) % mod;
}
}
}
return res;
}
jz ksm(jz a, int b) {
jz ans;
ans.build_E();
while (b) {
if (b & 1)ans = ans * a;
a = a * a;
b >>= 1;
}
return ans;
}
jz mt, tmp;
jz p;
int a[N];
vector<pair<int, int>> to[N];
void solve() {
mt.clear();
tmp.clear();
p.clear();
cin >> n >> m >> l >> r;
int k = n * 2;
for (int i = 1; i <= k + 1; i++) {
to[i].clear();
a[i] = 0;
}
a[1] = 1;
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
to[u].push_back({v, w});
to[u + n].push_back({v + n, w});
}
for (int i = 1; i <= k; i++) {
for (auto p: to[i]) {
int v = p.first;
int w = p.second;
if (v <= k)
a[v] = (a[v] + a[i] * w % mod) % mod;
}
a[k + 1] = (a[k + 1] + a[i]) % mod;
}
for (int i = n + 1; i <= k; i++) {
for (auto p: to[i]) {
int v = p.first;
int w = p.second;
if (v > k) {
mt.a[v % k][i] = (mt.a[v % k][i] + w) % mod;
}
}
}
for (int i = 1; i <= k; i++) {
for (auto p: to[i]) {
int v = p.first;
int w = p.second;
if (v <= k) {
for (int j = 1; j <= k; j++) {
mt.a[v][j] = (mt.a[v][j] + mt.a[i][j] * w % mod) % mod;
}
}
}
}
for (int i = 1; i <= k + 1; i++) {
mt.a[k + 1][i] = 1;
}
for (int i = 1; i <= 2 * n + 1; i++) {
p.a[i][1] = a[i];
}
int suml = 0, sumr = 0;
int yl = (l - 1) % k + 1;
int yr = (r - 1) % k + 1;
l = (l - 1) / k;
r = (r - 1) / k;
tmp = ksm(mt, l);
tmp = tmp * p;
suml = tmp.a[2 * n + 1][1] % mod;
for (int i = 1; i < yl; i++) suml = (suml + tmp.a[i][1]) % mod;
tmp = ksm(mt, r);
tmp = tmp * p;
sumr = tmp.a[2 * n + 1][1]% mod;
for (int i = 1; i <= yr; i++) sumr = (sumr + tmp.a[i][1]) % mod;
cout << (sumr + mod - suml) % mod << '\n';
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
方法二:待补充
1009-关于 agKc 实在不喜欢自动化于是啥都自己合成这件事
这题题意不太清楚,主要要注意几点
赛时有人询问了,不使用管理员服务的情况下,在有限的时间内可以制作出该物品,所以不存在环,因为每个物品只会作为一个配方的原料,所以形成的图形一定是森林。
考虑从最终要得到的节点开始做树形dp,
设a[u]表示u节点在它所属的配方中需要的份数,t[u]表示一些原材料需要的时间,b[u]表示制作所有所需要的u节点需要的时间,可以通过a[u]*b[v]转移得到,v是u的儿子节点。
对于最终产物的节点,如果它的儿子节点中只有一个是大于1e9,那么显然一定对这个动用管理员权力,如果有两个那么一定不成立,如果一个都没有,那么就删去权值最大的一棵子树。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
const int mod=998244353;
int n,k;
int a[N];
int t[N];
int b[N];
vector<int>to[N];
void dfs(int u){
if(t[u]!=0){
b[u]=a[u]*t[u];
if(b[u]>(int)1e9)b[u]=-1;
return;
}
for(auto v:to[u]){
dfs(v);
if(b[u]==-1)continue;
if(b[v]==-1){
b[u]=-1;
continue;
}
b[u]+=a[u]*b[v];
if(b[u]>(int)1e9||a[u]*b[v]>(int)1e9)b[u]=-1;
}
if(u==k){
int cnt=0;
b[u]=0;
int mmax=0;
for(auto v:to[u]){
if(b[v]==-1){
if(cnt==0)cnt++;
else{
b[u]=-1;
break;
}
continue;
}
b[u]+=b[v];
mmax=max(mmax,b[v]);
if(b[u]-mmax>(int)1e9){
b[u]=-1;
break;
}
}
if(b[u]!=-1&&cnt==0)b[u]-=mmax;
if(b[u]>(int)1e9)b[u]=-1;
}
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++){
int op;
cin>>op;
if(op==0){
cin>>t[i];
}else{
int m;
cin>>m;
for(int j=1;j<=m;j++){
int w,v;
cin>>w>>v;
a[v]=w;
to[i].push_back(v);
}
}
}
a[k]=1;
dfs(k);
if(t[k]!=0)b[k]=t[k];
if(b[k]==-1){
cout<<"Impossible"<<'\n';
}else{
cout<<b[k]<<'\n';
}
for(int i=1;i<=n;i++){
a[i]=0;
b[i]=0;
t[i]=0;
to[i].clear();
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while (T--) solve();
return 0;
}
1010-故障机器人想活下去
按序遍历敌人,记录当前的总战损,用大根堆记录遭遇敌人的战损,每次需要使用烟雾弹时对堆顶的敌人使用,将其从堆中弹出并在总战损中减去,直到烟雾弹用完故障机器人死亡或故障机器人活到了第n场战斗结束时找到答案。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10;
int n, x, k;
int a[N];
void solve() {
cin >> n >> x >> k;
priority_queue<int> q;
for (int i = 1; i <= n; i++)cin >> a[i];
if (k > n) {
cout << n << '\n';
return;
}
int ans = 0;
int res = 0;
for (int i = 1; i <= n; i++) {
res += a[i];
q.push(a[i]);
if (res >= x && k > 0) {
k--;
res-=q.top();
q.pop();
}
if(res<x){
ans++;
}
}
cout<<ans<<'\n';
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while (T--)solve();
return 0;
}
1011-蛋糕上的草莓是蛋糕的灵魂
要使草莓的大小尽量大,草莓切的刀数就要尽量少。
为了保证每块蛋糕上都有草莓且数量一样,必须保证最终的草莓数是蛋糕数的倍数。不切蛋糕的情况下,蛋糕数的因数较少,草莓数可以切尽量少的刀凑成蛋糕数的倍数。
设草莓的初始个数为 x,切 n 刀,则草莓数为: 2nx,蛋糕数为 y ,最终的草莓数是蛋糕数的c倍,c是正整数。 所以2nx=cy,可以求n,要保证n尽量小,n为正整数,应该用gcd来化简n的求值式子,
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
const int mod=998244353;
void solve(){
int x,y;
cin>>x>>y;
int g1=__gcd(x,y);
if((y/g1)%2&&g1!=y) x*=2;
cout<<y<<' '<<x/g1<<'\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}