(i-1)+(n-j)。
但是如果直接暴力枚举i,j,然后用线段树维护其对哪些k有贡献,复杂度是O(n2logn)的,T飞。
这类“看起来要枚举i,j算贡献”题的另一个套路又体现在这题里了,我们可以只枚举一边,然后用某些数据结构来解决问题。
对于这题(为方便考虑,假设ip[j]的情况可以将数组反过来再算一遍),我们先枚举左侧的i,它会与在其右侧且p[j]>p[i]的 j 对[p[i]+1, p[j]-1]之间的k产生相同的贡献(2i-1*2n-j),注意到虽然区间右侧不同,但是区间左侧都是从p[i]开始的,这是开始贡献的情况。枚举右侧的j,找到在其左侧且p[i] 因此我们可以采用差分的方法计算答案,算出k为x和k为x-1时两者的差距delta,就可以维护答案了。具体而言,枚举左侧i时,对在其右侧符合要求的 j 的贡献求和(可以用树状数组,从右往左枚举i,枚举完某点后把这个点作为右端的 2n-j 加到树状数组上),然后贡献和加到delta[p[i]+1]上。枚举右侧j时,对在其左侧符合要求的 i 的贡献求和(树状数组,从左往右枚举j),然后贡献和减到delta[p[j]-1]上。
这是只考虑p[i]
#pragma GCC optimize(3)
#include
#define inf 500000
#define Inf 223372036854775807
#define maxn 200100
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const double eps=1e-10;
const double PI=acos(-1.0);
ll n, num[maxn], mi[maxn]={1};
ll delta[maxn];
ll tr[maxn+5];
void add(ll x,ll v)
{
for (ll i=x; i<maxn; i+=(i&-i))
tr[i]=(tr[i]+v)%mod;
}
ll query(ll x)
{
ll ans=0;
for (ll i=x; i>0; i-=(i&-i))
ans=(ans+tr[i])%mod;
return ans;
}
void solve()
{
memset(tr, 0, sizeof(tr));
for (int i=n; i>=1; i--)
{
ll q=(query(n)-query(num[i]+1)+mod)%mod;
q=q*mi[i-1]%mod;
delta[num[i]+1]=(delta[num[i]+1]+q)%mod;
add(num[i], mi[n-i]);
}
memset(tr, 0, sizeof(tr));
for (int i=1; i<=n; i++)
{
if (num[i]>2)
{
ll q=query(num[i]-2);
q=q*mi[n-i]%mod;
delta[num[i]]=(delta[num[i]]-q+mod)%mod;
}
add(num[i], mi[i-1]);
}
}
int main()
{
cin>>n;
for (int i=1; i<=n; i++)
{
cin>>num[i];
mi[i]=mi[i-1]*2%mod;
}
solve();
reverse(num+1, num+1+n);
solve();
ll ans=0;
for (int i=1; i<=n; i++)
{
ans=(ans+delta[i])%mod;
cout<<ans<<"\n";
}
return 0;
}
F 草莓
题意:
农场为一个n*m的棋盘,wls初始位于第x行第y列,初始每个格子没草莓,每天早上每个格子长一个草莓,每天下午wls可以上下左右移动一次(或不动),每天晚上收获当前格子草莓,问k天后最多收多少草莓。
解题思路:
假设被走过的格子总数为x,答案上界就是这些格子里最后剩下的草莓为0,1,2…x-1。
假设行比列少(即n 当n>1时一定可以达到上界(k>n*m一直停在原地到最后n*m天走一个哈密顿路径,k 当n==1时就需要仔细考虑,若k小于y到两端距离长的那一段(假设到左端比较近),则向右直接走出k个格子的长度最优,若大于到右端距离则可以先向左走几步,然后向右一直走到最右点最优,若可以向左走到头在向右走到头则可以走满m个格子,这其中细节的把控可以参考代码。
#pragma GCC optimize(3)
#include
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 1001000
using namespace std;
typedef long long ll;
const ll mod=998244353;
const long double eps=1e-9;
const long double PI=acos(-1.0);
ll T, n, m, x, y, k, inv=499122177;
ll cal(ll l, ll r)
{
l%=mod, r%=mod;
return (l+r)*((r-l+mod+1)%mod)%mod*inv%mod;
}
int main()
{
cin>>T;
while (T--)
{
cin>>n>>m>>x>>y>>k;
if (n>m) swap(n, m), swap(x, y);
if (n>1)
{
if (k>=n*m) cout<<cal(k-n*m+1, k)<<"\n";
else cout<<cal(1, k)<<"\n";
}
else
{
ll sho=max(min(y-1, m-y)-1, 0ll), len;
if (k<=m-sho) len=k;
else len=m-sho+(k-m+sho)/2;
if (k>=sho+m) len=m;
cout<<cal(k-len+1, k)<<"\n";
}
}
return 0;
}
G 草莓2
题意:
和F题类似,不过每天早上收草莓,每天下午wls可以上下左右移动一次(或不动),每天晚上长草莓,问k天后最多收多少草莓。
nm范围小且一定为偶数,初始每个格子里有草莓。
解题思路:
kn*m时因为n和m都是偶数,所以一直走哈密顿回路,答案一定是最优的。
#pragma GCC optimize(3)
#include
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 5010
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const long double eps=1e-9;
const long double PI=acos(-1.0);
ll n, m, x, y, k, ans, a[20][20];
ll nxt[5][2]={-1, 0, 1, 0, 0, -1, 0, 1, 0, 0};
void dfs(ll nx, ll ny, ll res, ll t)
{
res+=a[nx][ny]+t-1;
if (t==k) {ans=max(ans, res); return ;}
ll buf=a[nx][ny];
a[nx][ny]=0;
ll tx, ty;
for (ll i=0; i<5; i++)
{
tx=nx+nxt[i][0], ty=ny+nxt[i][1];
if (tx<1 || tx>n || ty<1 || ty>m) continue;
dfs(tx, ty, res, t+1);
}
a[nx][ny]=buf;
}
ll solve2()
{
ll res=0;
for (ll i=1; i<=n; i++)
for (ll j=1; j<=m; j++)
res+=a[i][j];
res+=n*m*(n*m-1)/2;
k-=n*m;
res+=k*n*m;
return res;
}
int main()
{
cin>>n>>m>>x>>y>>k;
for (ll i=1; i<=n; i++)
for (ll j=1; j<=m; j++)
cin>>a[i][j];
if (k<n*m)
dfs(x, y, 0, 1);
else ans=solve2();
cout<<ans;
return 0;
}
H 游戏
题意:
n个数,每次当剩余数的数量大于1时随机取两个数删掉,若这两个数互质则得一分,问最后得分的期望值。
解题思路:
每一个数对被取到的概率相等,因此统计有几对数互质就可以算出期望。
#pragma GCC optimize(3)
#include
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 5010
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const long double eps=1e-9;
const long double PI=acos(-1.0);
ll n, fz, fm;
int main()
{
cin>>n;
for (ll i=1; i<=n; i++)
for (ll j=i+1; j<=n; j++)
if (__gcd(i, j)==1) fz++;
fz*=n/2, fm=(n-1)*n/2;
if (n!=1) cout<<fz/__gcd(fz, fm)<<"/"<<fm/__gcd(fz, fm)<<endl;
else cout<<"0/1"<<endl;
return 0;
}
J King
题意:
向下取整符号不会打orz,点这里看题
解题思路:
对于序列中第二个及以后的位置i都有一个从胖子串的串首的到的j的一个取值范围:( a[i]/(a[i-1]+1), (a[i]+1)/a[i-1] ),因此只要枚举i,把它当成胖子串的串首,然后二分串尾。用线段树维护每个点的这个区间,合并的时候左端点取较大者,右端点取较小者。check的时候检查串首到串尾间点区间合并后左端是否小于右端,串首能得到的j是否在与区间有交即可检验是否合法。
#pragma GCC optimize(3)
#include
#define inf 500000
#define Inf 223372036854775807
#define maxn 100100
using namespace std;
typedef long long ll;
const ll mod=998244353;
const double eps=1e-10;
const double PI=acos(-1.0);
int n, ans=1;
double a[maxn], mij[maxn], mxj[maxn];
struct Seg
{
double mi, mx;
}tr[maxn<<2];
Seg pushup(Seg x, Seg y)
{
Seg temp;
temp.mi=max(x.mi, y.mi);
temp.mx=min(x.mx, y.mx);
return temp;
}
void build(int l=2, int r=n, int pos=1)
{
if (l==r)
{
tr[pos].mi=mij[l], tr[pos].mx=mxj[l];
return ;
}
int mid=(l+r)/2;
build(l, mid, pos<<1), build(mid+1, r, pos<<1|1);
tr[pos]=pushup(tr[pos<<1], tr[pos<<1|1]);
}
Seg query(int L, int R, int l=2, int r=n, int pos=1)
{
if (l>=L && r<=R) return tr[pos];
int mid=(l+r)/2;
if (R<=mid) return query(L, R, l, mid, pos<<1);
else if (L>mid) return query(L, R, mid+1, r, pos<<1|1);
else return pushup(query(L, R, l, mid, pos<<1), query(L, R, mid+1, r, pos<<1|1));
}
int check(int fi, int ov)
{
Seg temp=query(fi+1, ov);
if (temp.mi>temp.mx+eps) return 0;
if (temp.mi>a[fi]+1+eps || temp.mx<a[fi]-eps) return 0;
return 1;
}
int main()
{
cin>>n;
for (int i=1; i<=n; i++)
cin>>a[i];
for (int i=2; i<=n; i++)
mij[i]=a[i]/(a[i-1]+0.999999), mxj[i]=(a[i]+0.999999)/a[i-1];
if (n>1) build();
for (int i=1; i<n; i++)
{
int l=i+1, r=n, mid;
while (l<r)
{
mid=(l+r)/2;
if (check(i, mid)) l=mid+1;
else r=mid;
}
if (!check(i, l)) l--;
ans=max(ans, l-i+1);
}
cout<<ans;
return 0;
}
K 修炼
题意:
有两个人物,其初始能力值为v1,v2,此外每天还会分别提升a1,a2的能力,此外每天在能力提升前还可以选择让a1增加1或让a2增加1。
给出q对b1,b2,分别输出对于每对b要使v1>=b1, v2>=b2需要的最少天数。
解题思路:
首先,题目符合二分的性质,可以二分天数。
对k天进行check看是否符合要求,v1至少会增加k*a1的能力,v2至少会增加k*a2的能力,此外在第i天选择让a1增加1或让a2增加1等价与让某个人最终能力增加k-i+1(因为剩余的每天都会多得到一点能力)。因此只要计算b1-k*a1和b2-k*a2(小于0则取0)之和是否小于 (1+k)*k/2即可。
#pragma GCC optimize(3)
#include
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 5010
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const long double eps=1e-9;
const long double PI=acos(-1.0);
ll n, a1, a2, b1, b2, ans=inf;
ll check(ll k)
{
ll B1=b1-k*a1, B2=b2-k*a2;
B1=max(B1, 0ll), B2=max(B2, 0ll);
if (k*(k+1)/2>=B1+B2) return 1;
return 0;
}
int main()
{
cin>>a1>>a2>>n;
for (int i=1; i<=n; i++)
{
cin>>b1>>b2;
ll l=1, r=inf, mid;
while (l<r)
{
mid=(l+r)/2;
if (check(mid)) r=mid;
else l=mid+1;
}
ans=min(ans, l);
}
cout<<ans;
}
L 图
题意:
n个点(n<=20)的图,初始每个点都有黑色或白色,下一轮点的颜色取决与连向它的点有几个为黑色,若有奇数个,则这个点下一轮为黑,偶数个则下轮为白。
q次询问,每次询问给出x,k。输出x号点至少要到多少轮才能至少出现过k次黑色
解题思路:
点很少,可以用状压表示图的状态。
容易观察出,经过一定轮数后(也可能一开始就处于循环)一定会进入循环状态,我们只要找到在哪里开始循环,记录一下过程中出现黑色的次数,求答案的时候稍微搞搞就ok了。
我的过程是先判断是否无解(即循环内x号点不出现黑色且进入循环前也不够k次黑色),然后判断是否进入循环。
未进入循环直接二分位置。
若进入循环,可以先判断循环了几次,然后再二分找最后一次在循环里的位置。
#pragma GCC optimize(3)
#include
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 1001000
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const long double eps=1e-9;
const long double PI=acos(-1.0);
vector<int> edge[25];
int vis[maxn<<2];
int n, m, q;
int cnt=1, col[maxn][22], loop[22], loopst;
int get_col(int k)
{
for (int i=1; i<=n; i++)
for (auto j: edge[i])
col[k][i]+=col[k-1][j];
for (int i=1; i<=n; i++)
col[k][i]&=1;
int sta=0;
for (int i=1; i<=n; i++)
if (col[k][i]) sta|=(1<<i);
return sta;
}
int find_(int x, int k)
{
int l=1, r=cnt, mid;
while (l<r)
{
mid=(l+r)/2;
if (col[mid][x]>=k) r=mid;
else l=mid+1;
}
return l;
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>n>>m>>q;
for (int i=1; i<=n; i++)
cin>>col[1][i];
int sta=0;
for (int i=1; i<=n; i++)
{
if (col[1][i]) sta|=(1<<i);
}
vis[sta]=1;
for (int i=1, u, v; i<=m; i++)
{
cin>>u>>v;
edge[v].push_back(u);
}
while (true)
{
int sta=get_col(++cnt);
if (vis[sta]) {cnt--, loopst=vis[sta]; break;}
vis[sta]=cnt;
}
for (int i=loopst; i<=cnt; i++)
for (int j=1; j<=n; j++)
loop[j]+=col[i][j];
for (int i=1; i<=cnt; i++)
for (int j=1; j<=n; j++)
col[i][j]+=col[i-1][j];
for (int qq=1; qq<=q; qq++)
{
int x; ll k;
cin>>x>>k;
if (k==0)
{
cout<<0<<"\n";
continue;
}
if (k>col[cnt][x] && loop[x]==0)
{
cout<<-1<<"\n";
continue;
}
if (k<=col[cnt][x])
{
cout<<find_(x, k)-1<<"\n";
}
else
{
ll res=0;
k-=col[loopst-1][x];
if (k%loop[x]==0)
{
res+=(k/loop[x]-1)*(cnt-loopst+1);
k=loop[x];
}
else res+=k/loop[x]*(cnt-loopst+1), k%=loop[x];
k+=col[loopst-1][x];
res+=find_(x, k);
cout<<res-1<<"\n";
}
}
return 0;
}