目录
A. Integer Moves
- 题意:
初始时在点(x,y) ,如果(x,y)与点(x1,y1)满足如下条件,那么可以从(x,y)转移至(x1,y1):
即两点之间的距离为整数,即可转移。
请你输出从(x,y)----->(0,0)的最小步数。
- 思路:
我们可以证明,最大步数不会超过2:
- 设初始点为(x,y)
- 我们可以选择这两种方案进行转移。
- (x,y)-->(x,0)-->(0,0),(x,y)-->(0,y)-->(0,0)
- 显然沿着坐标轴移动距离一定是整数,所以最大步数不会超过2。
那么我们只需要分析步数=1和步数=0的情况:
1.步数=1
1)初始点在(x,0)或者(0,y)上。
2)从点(x,y)到(0,0)的距离为整数。
2.步数=0
1)初始点在(0,0)
特别声明(判断浮点数是整数的代码):
long double temp = sqrt(x*x+y*y); if(temp - (int)temp == 0)std::cout<<1<<'\n';
- 时间复杂度:
O(1)
AC代码:
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
const int N = 2e5+10,M = 2 * N,INF = 0x3f3f3f3f,mod = 1e9+7;
void solve()
{
int x,y;
std::cin>>x>>y;
if(x==0&&y==0)std::cout<<0<<'\n';
else if(x==0||y==0)std::cout<<1<<'\n';
else
{
long double temp = sqrt(x*x+y*y);
if(temp - (int)temp == 0)std::cout<<1<<'\n';
else std::cout<<2<<'\n';
}
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int T;
std::cin>>T;
while(T--)
{
solve();
}
return 0;
}
B. XY Sequence
- 题意:
给定4个数n,B,x,y。
构造一个长度为n+1的序列,满足如下条件:
- 或
要求所构造出的序列满足:
最大,并输出这个最大值。
- 思路:
既然要最大,那么贪心即可。
如果:
那么就令
反之
最后求和即可。
- 时间复杂度:
O(n)
AC代码:
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
const int N = 2e5+10,M = 2 * N,INF = 0x3f3f3f3f,mod = 1e9+7;
void solve()
{
ll res = 0;
int n,b,x,y;
std::cin>>n>>b>>x>>y;
int temp = 0;
for(int i = 1 ; i <= n ; i++)
{
if(temp + x <= b)temp += x;
else temp -= y;
res += temp;
}
std::cout<<res<<'\n';
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int T;
std::cin>>T;
while(T--)
{
solve();
}
return 0;
}
C. Bracket Sequence Deletion
- 题意:
给定一个由 '(' 和 ')'组成的字符串,我们现在要删除这个字符串的最短好前缀,输出删除的次数,以及最后留下来的无法删除的字符个数
好前缀是满足下列条件之一的前缀:
- 这个前缀是这个正常的括号
- 这个前缀是回文
- 思路:
有两种情况:
1.当前字符串的第一个字符是 '(':
当一个字符是 '(' 时,无论下一个字符是什么,一定可以满足好前缀中的一个条件。
() 或((
2.当前字符串的第一个字符是 ')':
首先,这个前缀一定不可能是正常的括号,所以我们只需要找回文即可。
我们很容易发现,第一个字符一定会与后面的第一个' ) '之间形成回文。
把括号换成a和b可能更容易观察:
abbbaba aa abbabababa
所以只需要按照上面的规则删去前缀即可,如果无法满足上面的条件,那么剩下的字符无法被删除。
- 时间复杂度:
O(n)
AC代码:
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
const int N = 2e5+10,M = 2 * N,INF = 0x3f3f3f3f,mod = 1e9+7;
void solve()
{
int n;
std::string s;
std::cin>>n>>s;
s = '#' + s;
int res = 0,i = 1;
while(i <= n)
{
if(s[i]=='(')
{
if(i < n)res++,i+=2;
else break;
}
else
{
int j = i + 1;
while(j < n && s[j] =='(')j++;
if(j <= n && s[j]==')')res++,i = j + 1;
else break;
}
}
std::cout<<res<<' '<<n - i + 1<<'\n';
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int T;
std::cin>>T;
while(T--)
{
solve();
}
return 0;
}
D. For Gamers. By Gamers.
- 题意:
小明有C元钱。
给定n种类型的部队,购买第 i 种的部队:
1.花费 ci 元
2.获得(增加) di 的攻击力
3.获得(增加) hi 的血量
(一种部队可以多次购买)
现有m种怪物,第 i 种怪物具有两个属性:
1.具有 Di 的攻击力
2.具有 Hi 的血量
在攻击每一个怪物之前,小明的金额,攻击力和血量的数值都会重置。
如果小明的部队可以打赢怪物,那么小明杀死怪物的速度必须要严格快于怪物杀死小明的速度。
对于每一只怪物,输出杀死这只怪物所需要花费的最小金额,如果不存在购买方案可以杀死怪物,输出-1。
- 思路:
小明要更快得杀死怪物,即满足下式:
设怪物攻击力Di,血量Hi。
小明购买部队后攻击为d,血量h。
那么我们可以把血量和攻击力相乘,作为战斗力保存。
又因为C的范围不会太大,不超过1e6,所以我们可以预处理出花费 i 元可以获得的最大战斗力。
for(int i = 1 ; i <= n ; i++) { ll w,x,y; std::cin>>w>>x>>y; f[w] = std::max(x*y,f[w]); } for(int i = 1 ; i <= C ; i++) for(int j = 2 ; j * i <= C ; j++) f[i*j] = std::max(f[i*j],f[i]*j);
这里花费的时间是O(ClogC)
在预处理好后,对于第 i 个怪物,我们可以二分找到大于怪物战斗力所需要的最小花费。
因为需要二分,所有我们要保证 f[i] 是单调的,所以需要额外处理一下。
for(int i = 1 ; i <= C ; i++)f[i] = std::max(f[i],f[i-1]);
- 时间复杂度:
O((C+m)logC)
AC代码:
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
const int N = 3e5+10,M = 1e6 + 10,INF = 0x3f3f3f3f,mod = 1e9+7;
ll f[M];
void solve()
{
int n,C;
std::cin>>n>>C;
for(int i = 1 ; i <= n ; i++)
{
ll w,x,y;
std::cin>>w>>x>>y;
f[w] = std::max(x*y,f[w]);
}
for(int i = 1 ; i <= C ; i++)
for(int j = 2 ; j * i <= C ; j++)
f[i*j] = std::max(f[i*j],f[i]*j);
for(int i = 1 ; i <= C ; i++)f[i] = std::max(f[i],f[i-1]);
int m;
std::cin>>m;
while(m--)
{
ll x,y;
std::cin>>x>>y;
if(x*y >= f[C])std::cout<<-1<<' ';
else
{
std::cout<<std::upper_bound(f+1,f+1+C,x*y) - f<<' ';
}
}
std::cout<<'\n';
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
// int T;
// std::cin>>T;
// while(T--)
{
solve();
}
return 0;
}
E. Star MST
- 题意:
给定一个n个点的无向完全图,每条边的权值在1~k之间。
有多少种方案给每条边分配权值,使得该图满足以下条件:
最小生成树的权值和 = 所有与1号点相连的边的权值和。
- 思路:
我们考虑动态规划 dp[w][i] :
1.dp表示方案数。
2.w 表示当前所有边的权值小w 。
3.i 表示已经为 i 个点分配好了边权。
那么怎么进行状态转移呢?
2. i 个点中选 j 个点。
3,4 .在 j 个点内部连边,内部和外部连边。
其中(k+1-w)是可选的边权值。
特别声明:
1.我们可以预处理出所有的组合数
for(int i = 0 ; i < n ; i++) { comb[i][0] = comb[i][i] = 1; for(int j = 1 ; j < i ; j++) comb[i][j] = (comb[i-1][j-1] + comb[i-1][j]) % mod; }
2.我们可以在每次计算之前预处理出所有的(k+1-w)的幂次
pw[0] = 1; for(int i = 1 ; i <= n * n ; i++)pw[i] = (pw[i-1]*(k + 1 - w)) % mod;
3.我们可以发现,我们在计算权值小于等于w的方案数时,我们只关系权值小于等于(w-1)的方案数,所以我们可以使用滚动数组,用一维的dp[i]表示即可,并使用last[i]记录(w-1)的方案数。
- 时间复杂度:
AC代码:
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
const int N = 3e2+10,M = 1e6 + 10,INF = 0x3f3f3f3f,mod = 998244353;
ll dp[N],pw[N*N],last[N],comb[N][N];
void solve()
{
int n,k;
std::cin>>n>>k;
dp[0] = 1;
for(int i = 0 ; i < n ; i++)
{
comb[i][0] = comb[i][i] = 1;
for(int j = 1 ; j < i ; j++)
comb[i][j] = (comb[i-1][j-1] + comb[i-1][j]) % mod;
}
for(int w = 1 ; w <= k ; w++)
{
for(int i = 0 ;i < n ; i++)last[i] = dp[i],dp[i] = 0;
pw[0] = 1;
for(int i = 1 ; i <= n * n ; i++)pw[i] = (pw[i-1]*(k + 1 - w)) % mod;
for(int i = 0 ; i < n ; i++)
for(int j = 0 ; i - j >= 0 ; j++)
dp[i] = (dp[i] + comb[i][j] * pw[j*(j-1)/2 + (i - j) * j] % mod * last[i-j]) % mod;
}
std::cout<<dp[n-1];
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
// int T;
// std::cin>>T;
// while(T--)
{
solve();
}
return 0;
}
F. Words on Tree
- 题意:
给定一棵树
每个节点储存一个字母
现有q个要求:
每个要求有u,v两个节点编号和一个字符串S
现在给树中的每个节点赋一个字母,满足如下条件:
对每一个要求,把从节点u到节点v路径中所有的字母按顺序取出,所组成的字符串是S。
或者把从节点v到节点u路径中所有的字母按顺序取出,所组成的字符串是S。
- 思路:
首先:
1.取出路径--->采用类似于述树链剖分的策略
void dfs(int u,int father,int depth) { dep[u] = depth,fa[u] = father; for(int i = h[u] ; ~i ; i = ne[i]) { int v = e[i]; if(v==father)continue; dfs(v,u,depth+1); } } std::vector<int> getPath(int u,int v) { std::vector<int> L,R; while(u!=v) { if(dep[u] < dep[v]) { R.push_back(v); v = fa[v]; } else { L.push_back(u); u = fa[u]; } } L.push_back(u); L.insert(L.end(),R.rbegin(),R.rend()); return L; }
2.选取正序的字符串或是逆序的字符串--->2-SAT
我们这里按照要求的顺序,给树中每一个节点确定一个答案,再根据2-SAT来建立关系
这里是建图过程:
for(int i = 0 ; i < q ; i++) { int a,b; std::string s; std::cin>>a>>b>>s; a--,b--; query[i] = {a,b,s}; std::vector<int> temp = getPath(a,b); for(int j = 0 ; j < temp.size() ; j++) { ans[temp[j]][0] = s[j]; ans[temp[j]][1] = s[temp.size() - j - 1]; } } for(int i = 0 ; i < q ; i++) { int x,y; std::string s; std::tie(x,y,s) = query[i]; std::vector<int> temp = getPath(x,y); for(int j = 0 ; j < temp.size() ; j++) for(int o = 0 ; o < 2 ; o++) { if(ans[temp[j]][o] != s[j]) { add(hs,temp[j] * 2 + o,(i+n) * 2 + 1); add(hs,(i+n) * 2 + 0,temp[j] * 2 + !o); } if(ans[temp[j]][o] != s[temp.size() - 1 - j]) { add(hs,temp[j] * 2 + o,(i+n) * 2 + 0); add(hs,(i+n) * 2 + 1,temp[j] * 2 + !o); } } }
建完图后跑一边tarjan即可。
- 时间复杂度:
时间复杂度上限由tarjan算法决定。
本题中每个要求的每个字母都可能产生3对关系,即建立6条边,所以时间复杂度:
(m不会超过n的6倍)
AC代码:
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
const int N = 4e5+10,M = N * 2,INF = 0x3f3f3f3f,mod = 998244353;
int h[N],hs[N*4],ne[M*4],e[M*4],idx;
int dfn[N*4],low[N*4],id[N*4],stk[N*4],top,ts,scc;
int fa[N],dep[N];
bool book[N*4],res[N];
int n,q;
void init()
{
memset(h,-1,sizeof h);
memset(hs,-1,sizeof hs);
}
void add(int h[],int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void dfs(int u,int father,int depth)
{
dep[u] = depth,fa[u] = father;
for(int i = h[u] ; ~i ; i = ne[i])
{
int v = e[i];
if(v==father)continue;
dfs(v,u,depth+1);
}
}
void tarjan(int u)
{
dfn[u] = low[u] = ++ts;
stk[++top] = u,book[u] = true;
for(int i = hs[u] ; ~i ; i = ne[i])
{
int v = e[i];
if(!dfn[v])
{
tarjan(v);
low[u] = std::min(low[u],low[v]);
}
else if(book[v])low[u] = std::min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
int temp;
scc++;
do{
temp = stk[top--];
book[temp] = false;
id[temp] = scc;
}while(u!=temp);
}
}
std::vector<int> getPath(int u,int v)
{
std::vector<int> L,R;
while(u!=v)
{
if(dep[u] < dep[v])
{
R.push_back(v);
v = fa[v];
}
else
{
L.push_back(u);
u = fa[u];
}
}
L.push_back(u);
L.insert(L.end(),R.rbegin(),R.rend());
return L;
}
void solve()
{
std::cin>>n>>q;
init();
for(int i = 1 ; i < n ; i++)
{
int a,b;
std::cin>>a>>b;
a--,b--;
add(h,a,b),add(h,b,a);
}
dfs(0,-1,1);
std::vector<std::array<char,2>> ans(n,{'a','b'});
std::vector<std::tuple<int,int,std::string>> query(q);
for(int i = 0 ; i < q ; i++)
{
int a,b;
std::string s;
std::cin>>a>>b>>s;
a--,b--;
query[i] = {a,b,s};
std::vector<int> temp = getPath(a,b);
for(int j = 0 ; j < temp.size() ; j++)
{
ans[temp[j]][0] = s[j];
ans[temp[j]][1] = s[temp.size() - j - 1];
}
}
for(int i = 0 ; i < q ; i++)
{
int x,y;
std::string s;
std::tie(x,y,s) = query[i];
std::vector<int> temp = getPath(x,y);
for(int j = 0 ; j < temp.size() ; j++)
for(int o = 0 ; o < 2 ; o++)
{
if(ans[temp[j]][o] != s[j])
{
add(hs,temp[j] * 2 + o,(i+n) * 2 + 1);
add(hs,(i+n) * 2 + 0,temp[j] * 2 + !o);
}
if(ans[temp[j]][o] != s[temp.size() - 1 - j])
{
add(hs,temp[j] * 2 + o,(i+n) * 2 + 0);
add(hs,(i+n) * 2 + 1,temp[j] * 2 + !o);
}
}
}
for(int i = 0 ; i < (n + q) * 2 ; i++)if(!dfn[i])tarjan(i);
for(int i = 0 ; i < n + q ; i++)
if(id[i*2] == id[i*2+1])
{
std::cout<<"NO";
return;
}
else res[i] = id[i*2+1] < id[i*2];
std::cout<<"YES\n";
for(int i = 0 ; i < n ; i++)std::cout<<ans[i][res[i]];
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
// int T;
// std::cin>>T;
// while(T--)
{
solve();
}
return 0;
}