前言
感觉很抽象啊,以前 edu 哪有过这么多的......
一、A. Passing the Ball
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
string s;
cin>>s;
s=" "+s;
vector<int>vis(n+1);
for(int i=1,cur=1;i<=n;i++)
{
vis[cur]=1;
cur+=(s[cur]=='L'?-1:1);
}
int ans=0;
for(int i=1;i<=n;i++)
{
ans+=vis[i];
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
没啥好说的,直接从 1 开始模拟一遍即可,中间用个表记录一下哪些人传到过即可。
二、B. Right Maximum
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<vector<int>>pos(n+1);
for(int i=n;i>=1;i--)
{
pos[a[i]].push_back(i);
}
int ans=0;
for(int i=n,l=n+1;i>=1;i--)
{
for(auto p:pos[i])
{
if(p<l)
{
l=p;
ans++;
}
}
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
不难发现被删除部分的长度是单调不减的。那么只需要倒着过一遍数组,记录每个数字出现的位置。此时就满足后出现的数优先被考虑。那么之后从大到小枚举每个数,用一个变量记录之前删除到的左边界。每次若当前数当前出现的位置在之前删除到的位置左侧,那么就需要删除一次,更新左边界,增加答案即可。
三、C. Spring
这题能过这么多人??!
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<typename T>
T gcd(T a,T b){
return b==0?a:gcd(b,a%b);
}
template<typename T>
T lcm(T a,T b){
return a*b/gcd(a,b);
}
void solve()
{
ll a,b,c,m;
cin>>a>>b>>c>>m;
auto calc=[&](ll base,ll x,ll y)->ll
{
ll p1=lcm(base,x);
ll p2=lcm(base,y);
ll p3=lcm(p1,y);
ll all=m/base;
ll d1=m/p1;
ll d2=m/p2;
ll d3=m/p3;
ll uni=d1+d2-d3;
ll ans=(all-uni)*6+(d1-d3)*3+(d2-d3)*3+d3*2;
return ans;
};
cout<<calc(a,b,c)<<" "<<calc(b,a,c)<<" "<<calc(c,a,b)<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
对于当前人的取水量,不难想到需要容斥一下。
那么就是先求出和其他两人的 lcm,还有三个人的 lcm,注意求三个人的时候防止溢出。那么自己去的次数 all,就是 m 除以自己的频率 base。之后,和第一个人重合的次数 d1 就是 m 除以和第一个人的 lcm,和第二个人的 d2 以及三个人的 d3 类似。所以并集就是 d1+d2-d3,那么自己单独的天数就是 all 减去并集,两个人的就是 d1-d3 和 d2-d3,乘一下即可。
四、D. Alternating Path
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
struct DSU{
vector<int>father;
//自定义
vector<int>sz;
DSU(int n){
father.assign(n,0);
sz.assign(n,1);
for(int i=0;i<n;i++){
father[i]=i;
}
}
int find(int i){
if(i!=father[i]){
father[i]=find(father[i]);
}
return father[i];
}
bool same(int x,int y){
return find(x)==find(y);
}
bool merge(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx==fy){
return false;
}
father[fx]=fy;
sz[fy]+=sz[fx];
return true;
}
};
void solve()
{
int n,m;
cin>>n>>m;
vector<vector<int>>g(n+1);
DSU dsu(n+1);
for(int i=1,u,v;i<=m;i++)
{
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
dsu.merge(u,v);
}
vector<int>vis(n+1);
vector<int>color(n+1);
auto dfs=[&](auto &&self,int u,int cur)->int
{
if(vis[u])
{
return color[u]==cur?0:-1;
}
vis[u]=1;
color[u]=cur;
int sum=cur;
for(auto v:g[u])
{
int res=self(self,v,cur^1);
if(res==-1)
{
return -1;
}
sum+=res;
}
return sum;
};
int ans=0;
for(int i=1;i<=n;i++)
{
if(i==dsu.find(i))
{
int sz=dsu.sz[i];
int res=dfs(dfs,i,0);
if(res!=-1)
{
ans+=max(res,sz-res);
}
}
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
举大量例子手玩可以发现,对于这条路径,要求第奇数个点作为边的出点,第偶数个点作为边的入点。那么若整张图合法,那么必然存在一部分点拥有的全是入度,另一部分点拥有的全是出度,且不存在一个点既有入度又有出度。
那么由于此时可以把所有点划分为不重叠的两部分,那么这就可以看作一张二分图,所有边从白色的点出发连向黑色的点。那么要求好点的个数最多,其实就是在求白色点和黑色点的最大值,因为必然可以通过翻转边的方向实现颜色转换。
所以在实现的时候先拿并查集维护连通性,再每次递归染色,判断是否是一张二分图,同时返回白色点的个数即可。
五、E. Sum of Digits (and Again)
纯史题啊,真就 Attention is all you need......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
string s;
cin>>s;
int n=s.length();
s=" "+s;
if(n==1)
{
cout<<s[1]<<endl;
return ;
}
vector<int>cnts(10);
for(int i=1;i<=n;i++)
{
cnts[s[i]-'0']++;
}
auto check=[&](int x)->int
{
vector<int>tmp=cnts;
int cur=x;
while(cur>9)
{
int sum=0;
while(cur)
{
sum+=cur%10;
if(--tmp[cur%10]<0)
{
return 0;
}
cur/=10;
}
cur=sum;
}
if(--tmp[cur]<0)
{
return 0;
}
int tot=0;
for(int i=1;i<=9;i++)
{
tot+=i*tmp[i];
}
return tot==x;
};
auto proc=[&](int x)->string
{
vector<int>tmp=cnts;
string ans;
int cur=x;
while(cur>9)
{
ans+=to_string(cur);
int sum=0;
while(cur)
{
sum+=cur%10;
tmp[cur%10]--;
cur/=10;
}
cur=sum;
}
ans+=to_string(cur);
tmp[cur]--;
string fir;
for(int i=1;i<=9;i++)
{
while(tmp[i]--)
{
fir+=char(i+'0');
}
}
while(tmp[0]--)
{
fir+='0';
}
return fir+ans;
};
const int MAXV=9*n;
for(int i=1;i<=MAXV;i++)
{
int res=check(i);
if(res)
{
cout<<proc(i)<<endl;
return ;
}
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
虽然第一个数可能很大,但注意到若整个字符串的长度为 n,第二个数再大也不会超过 9*n。所以就可以枚举第二个数,然后每次 check 是否合法。
那么 check 就是从第二个数开始,先一直模拟到最后,然后再看剩下数的累加和是否等于第二个数。如果合法,那么就再操作一遍,把最后拼好的字符串输出即可。注意不能每次都拼一遍字符串,否则会导致超时。
六、F. Sum of Fractions
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<class T>
constexpr T power(T a, ll b) {
T res = 1;
for (; b != 0; b /= 2, a *= a) {
if (b & 1) {
res *= a;
}
}
return res;
}
template<int M>
struct ModInt {
public:
constexpr ModInt() : x(0) {}
template<typename T>
constexpr ModInt(T x_) {
T v = x_ % M;
if (v < 0) {
v += M;
}
x = v;
}
constexpr int val() const {
return x;
}
constexpr ModInt &operator++() & {
x++;
if (x == M) {
x = 0;
}
return *this;
}
constexpr ModInt operator++(int) & {
ModInt res = *this;
++(*this);
return res;
}
constexpr ModInt &operator--() & {
if (x == 0) {
x = M - 1;
} else {
x--;
}
return *this;
}
constexpr ModInt operator--(int) & {
ModInt res = *this;
--(*this);
return res;
}
constexpr ModInt operator-() const {
ModInt res;
res.x = (x == 0 ? 0 : M - x);
return res;
}
constexpr ModInt inv() const {
return power(*this, M - 2);
}
constexpr ModInt &operator*=(const ModInt &rhs) &{
x = ll(x) * rhs.val() % M;
return *this;
}
constexpr ModInt &operator+=(const ModInt &rhs) &{
x += rhs.val();
if (x >= M) {
x -= M;
}
return *this;
}
constexpr ModInt &operator-=(const ModInt &rhs) &{
x -= rhs.val();
if (x < 0) {
x += M;
}
return *this;
}
constexpr ModInt &operator/=(const ModInt &rhs) &{
return *this *= rhs.inv();
}
friend constexpr ModInt operator*(ModInt lhs, const ModInt &rhs) {
lhs *= rhs;
return lhs;
}
friend constexpr ModInt operator+(ModInt lhs, const ModInt &rhs) {
lhs += rhs;
return lhs;
}
friend constexpr ModInt operator-(ModInt lhs, const ModInt &rhs) {
lhs -= rhs;
return lhs;
}
friend constexpr ModInt operator/(ModInt lhs, const ModInt &rhs) {
lhs /= rhs;
return lhs;
}
friend constexpr bool operator==(ModInt lhs, const ModInt &rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator<(ModInt lhs, const ModInt &rhs) {
return lhs.val() < rhs.val();
}
friend constexpr bool operator>(ModInt lhs, const ModInt &rhs) {
return lhs.val() > rhs.val();
}
friend constexpr bool operator<=(ModInt lhs, const ModInt &rhs) {
return lhs.val() <= rhs.val();
}
friend constexpr bool operator>=(ModInt lhs, const ModInt &rhs) {
return lhs.val() >= rhs.val();
}
friend constexpr bool operator!=(ModInt lhs, const ModInt &rhs) {
return lhs.val() != rhs.val();
}
friend constexpr std::istream &operator>>(std::istream &is, ModInt &a) {
ll i;
is >> i;
a = i;
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const ModInt &a) {
return os << a.val();
}
private:
int x;
};
template<int M, typename T = ModInt<M>>
struct Comb {
vector<T> fac;
vector<T> inv;
Comb(int n) {
fac.assign(n, 1);
for(int i=1;i<n;i++)
{
fac[i]=fac[i-1]*i;
}
inv.assign(n, 1);
inv[n-1]=fac[n-1].inv();
for(int i=n-2;i>=0;i--)
{
inv[i]=inv[i+1]*(i+1);
}
}
template<std::signed_integral U>
T P(U n, U m) {
if(n<m)
{
return 0;
}
return fac[n] * inv[n - m];
}
template<std::signed_integral U>
T C(U n, U m) {
if(n<m||m<0)
{
return 0;
}
return fac[n] * inv[n - m] * inv[m];
}
};
//power函数切记强转成 Z !!!!!
constexpr int M = 998244353;
using Z = ModInt<M>;
constexpr int N = 2e5+5;
Comb<M>comb(N);
template<std::signed_integral U>
Z P(U n, U m) {
return comb.P(n, m);
}
template<std::signed_integral U>
Z C(U n, U m) {
return comb.C(n, m);
}
void solve()
{
int n,m;
cin>>n>>m;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<ll>b(m+1);
for(int i=1;i<=m;i++)
{
cin>>b[i];
}
stack<int>stk;
vector<int>left(n+1);
for(int i=1;i<=n;i++)
{
while(!stk.empty()&&a[i]<=a[stk.top()])
{
int k=stk.top();
stk.pop();
left[k]=stk.empty()?0:stk.top();
}
stk.push(i);
}
while(!stk.empty())
{
int k=stk.top();
stk.pop();
left[k]=stk.empty()?0:stk.top();
}
vector<int>right(n+1);
for(int i=n;i>=1;i--)
{
while(!stk.empty()&&a[i]<a[stk.top()])
{
int k=stk.top();
stk.pop();
right[k]=stk.empty()?n+1:stk.top();
}
stk.push(i);
}
while(!stk.empty())
{
int k=stk.top();
stk.pop();
right[k]=stk.empty()?n+1:stk.top();
}
Z base=0;
for(int i=1;i<=n;i++)
{
int l=left[i];
int r=right[i];
base+=power(Z(a[i]),M-2)*i*(n-i+1);
}
vector<int>id(n+1);
for(int i=1;i<=n;i++)
{
id[i]=i;
}
sort(id.begin()+1,id.end(),[&](int x,int y)
{
return a[x]<a[y];
});
Z tot=0;
vector<Z>value(n+1);
for(int i=1;i<=n;i++)
{
int l=left[i];
int r=right[i];
value[i]=power(Z(a[i]),M-2)*(i-l)*(r-i);
tot+=value[i];
}
vector<Z>ans(m+1);
Z sum=0;
Z cur=0;
Z pre=0;
for(int i=1,j=1;i<=m;i++)
{
ans[i]=base;
while(j<=n&&a[id[j]]<=b[i])
{
int m=id[j];
int l=left[m];
int r=right[m];
pre+=value[m];
tot-=value[m];
sum+=Z(2-a[m])*(m-l)*(r-m);
cur+=Z(m-l)*(r-m);
j++;
}
ans[i]+=b[i]*tot-pre+sum+b[i]*cur;
}
for(int i=1;i<=m;i++)
{
cout<<ans[i]<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
首先考虑增加操作,当 时,对于
和
,交叉相乘有
和
。那么拆开化简有
和
。此时可以发现左侧是必然大于等于右侧的,所以就有
。
所以也就是说,在 时,肯定是优先选择增加分子的。那么当
时,肯定是优先选择增加分母的。
那么首先,因为分子最多增加 a-1 次,所以对于 的情况,肯定是让其变成
的。
之后,重点考虑 的情况。此时举大量例子手玩,再发动鬼脑 guess 一下可以发现,此时的最优策略必然不是先增加分子到
,再每次增加分母,而是选择先减少分母到
,再每次增加分子。因为前者一直带着分母,就导致每次增加的收益减小了。所以对于
的情况,最终的结果是
,即
。
所以观察这个式子,可以发现 a 越小最终产生的贡献越大,所以对于每个子数组,必然是一直增加其中的最小值。
之后对于这种区间问题,还是可以考虑贡献法。首先,不难计算出当 k=0,即不操作时的答案。因为每个数必然可以对每个包含自己的子数组贡献一次,所以就是每个数乘以其左右所有可能的位置。
之后,考虑讨论每个数作为子数组最小值产生 额外 贡献的次数。那么就还是需要用单调栈维护出左右两侧第一个大于当前数的位置。为了防止重复计算,这里考虑左侧严格大于,右侧大于等于。
对于当前 ,此时所有小于等于其的
就无法作为第一种情况产生贡献了。所以可以发现,在
增加的过程中,这些数是单调不减的。那么就可以考虑双指针,在对
从小到大排序后,从小到大枚举
,维护当前作为第二种情况产生贡献的数。
每次计算时,可以发现对于所有作为第一种情况产生贡献的数,其分子都是 k+1,所以其都会额外产生 k 倍的贡献。那么就可以考虑维护后缀所有 贡献的累加和,每次乘以 k 即可。
对于第二种情况的所有数,首先就需要在初始答案中,减去它们作为最小值的贡献,这个可以通过维护一个前缀和实现。之后,需要再把它们产生的新贡献加入,即 。那么和第一种情况类似,只需要维护出
产生的贡献 sum,以及每个数要乘的其左右两侧边界数的累加和 cur,那么每次只需要在 sum 的基础上加上 k 乘以 cur 即可。
总结
G 太变态了,听不懂一点......