A.Almost Correct
待补
B.Anticomplementary Triangle
待补
C.Carrot Trees
题意:给定一个长度为n的数组初始值均为0,给定一个常量k。定义以下两个区间操作
1 l r x 区间[l,r]加x/k
2 l r 区间[l,r]之间大于1的位置-1
求所有操作后 减的操作执行次数。
思路:一眼线段树,但是不知道怎么处理实数和记录等于0的位置。看了题解,借位的思路很妙,
首先如何去处理实数?因为k在所有操作中是一样的 所以我们可以将操作转化为区间加 x ,和区间内大于等于k的值 -k,这样就可以在整数里面操作了。
那么如何去记录所有位置减的次数?记录区间内小于k的数量,如果都是0则不作操作,如果都大于等于k则记录,这些显然会爆时间。题解使用了借位的思想(美丽的数学)
设 ,ai 为位置i真正的值,ci为解除0的限制的条件下线段树进行各个操作后的值,bi为将ci转变为ai所需要的倍数,也就是重新加上0这个限制条件。
考虑这样一个过程,对于操作1 ,我们之间加上x值就可以了,也就是
那么对于操作2 则要分情况
情况1
也就是 这种情况下我们之间 ,
本身的值已经足够去进行一次 -k 的操作,直接减去就好了,则是继承上一回的值。
情况2
也就是
本身的值不足以支持-k的操作,如果直接减去那么在0的限制下 这个等式就不成立了,所以我们要先向bi借一位来保证等式成立。所以 ,
可以看出 b是一个递增的变量,且这两者之间又某种神奇的关系。看了过程可以猜到
题解的证明十分清晰
所以我们只需要维护每个位置c的历史最小值即可。
//区间修改 单点查询历史最小值
#include<bits/stdc++.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
using namespace std;
const int N=1e6+5;
const ll md=1e9+7;
const ll inf=1e18;
const ll eps=1e-9;
const double E=2.718281828;
struct linetree{
ll lazy,min_lazy;//区间修改标签 区间修改历史最小标签
ll val,min_val;//区间最小值 区间历史最小值
}tr[N<<2];
ll n,m,k;
void up(int rt){
tr[rt].val=min(tr[rt<<1].val,tr[rt<<1|1].val);
tr[rt].min_val=min(tr[rt<<1].min_val,tr[rt<<1|1].min_val);
//和普通线段树一样正常上推信息
}
void down(int rt){
int ls=rt<<1,rs=rt<<1|1;
//历史最值线段树比普通线段树多一个历史标签,用来记录父亲操作的一个前缀最小或者最大
//然后根据现有的最小值和父亲下传的最小操作前缀更新历史最小值
//左儿子
tr[ls].min_lazy=min(tr[ls].min_lazy,tr[ls].lazy+tr[rt].min_lazy);
tr[ls].min_val=min(tr[ls].min_val,tr[ls].val+tr[rt].min_lazy);
tr[ls].lazy+=tr[rt].lazy;
tr[ls].val+=tr[rt].lazy;
//右儿子
tr[rs].min_lazy=min(tr[rs].min_lazy,tr[rs].lazy+tr[rt].min_lazy);
tr[rs].min_val=min(tr[rs].min_val,tr[rs].val+tr[rt].min_lazy);
tr[rs].lazy+=tr[rt].lazy;
tr[rs].val+=tr[rt].lazy;
tr[rt].lazy=tr[rt].min_lazy=0;//标签清空
}
//void build(int l,int r,int rt){
// if(l==r){
//
// }
//}
void updata(int l,int r,int rt,int pl,int pr,ll v){
if(pl<=l&&r<=pr){
tr[rt].min_lazy=min(tr[rt].min_lazy,tr[rt].lazy+v);
tr[rt].min_val=min(tr[rt].min_val,tr[rt].val+v);
tr[rt].lazy+=v;
tr[rt].val+=v;
return ;
}
down(rt);
int m=l+r>>1;
if(pl<=m){
updata(l,m,rt<<1,pl,pr,v);
}
if(pr>m){
updata(m+1,r,rt<<1|1,pl,pr,v);
}
up(rt);
}
ll queryr(int l,int r,int rt,int x){
// cout<<"query r "<<l<<" "<<r<<" "<<tr[rt].lazy<<" "<<tr[rt].val<<endl;
if(l==r){
return tr[rt].val;
}
int m=l+r>>1;
down(rt);
if(x<=m){
return queryr(l,m,rt<<1,x);
}else{
return queryr(m+1,r,rt<<1|1,x);
}
}
ll query(int l,int r,int rt,int x){
if(l==r){
return tr[rt].min_val;
}
int m=l+r>>1;
down(rt);
if(x<=m){
return query(l,m,rt<<1,x);
}else{
return query(m+1,r,rt<<1|1,x);
}
}
void pt(){
vector<int>num,num1;
for(int i=1;i<=n;i++){
num.push_back(queryr(1,n,1,i));
num1.push_back(query(1,n,1,i));
}
for(int v:num){
cout<<v<<" ";
}
cout<<endl;
for(int v:num1){
cout<<v<<" ";
}
cout<<endl;
}
void solve(){
//fucking and strange
cin>>n>>m>>k;
int op,l,r,x;
// build(1,n,1);
//初始值为0就懒得写build了 坏习惯
ll ans=0;
while(m--){
cin>>op>>l>>r;
if(op==1){//区间加 x
cin>>x;
updata(1,n,1,l,r,x);
// pt();
}else{// 区间减 k
ans+=(ll)(r-l+1);//有借位存在 所以我们可以先假定无0限制所有位置均可以减
updata(1,n,1,l,r,-k);
// pt();
}
}
for(int i=1;i<=n;i++){
ll c=query(1,n,1,i);
// cout<<c<<endl;
ans+= c%k==0?c/k: c/k-1;//记录的是历史最小值 所以c<=0 只有为负数的时候
//才要加上之前多减的次数。向下取整
}
cout<<ans<<endl;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
}
D.Chocolate
题意:有一块n*m的巧克力,每次玩家可以吃(1,1)到(i,j)这个矩形之间的巧克力,每个玩家在各自回合必须吃至少一个单位的巧克力,吃掉最后一块巧克力的人输掉比赛。
(从皇家翻译获得题意后反手喂给队里的博弈选手,就秒了?)
思路:先手直接将巧克力吃成一个L形,可以根据后手的操作去调整自己的操作(对称博弈?)所以只有巧克力为1*1的尺寸的时候先手没有办法去进行对称所以 后手赢
#include<bits/stdc++.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
using namespace std;
const int N=0;
const ll md=1e9+7;
const ll inf=1e18;
ll n,m;
void solve(){
//fucking and strange
cin>>n>>m;
if(n==1&&m==1){
cout<<"Walk Alone"<<endl;
}else{
cout<<"Kelin"<<endl;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
}
H.Matches
题意:给定两个数组 a b 长度为n 可以在任意数组内进行至多一次交换 定义
求最小的d
思路:一股浓浓的CF的味道,队友写了分类讨论有道理但是很麻烦。对于每一对 和我们可以看做一条线段 d就可以看作所有线段的长度和。然后去最小化d,如果不进行任何操作,d就是初始线段所有的长度。如果线段与线段之间由相交的区域,那么我们就可以减去这个区域*2的长度。
所以我们找到在合法交换下可以消除的最大相交区域。
根据和的关系进行分类
标记为a为右端点 b为左端点
标记为b为右端点 a为左端点
所有线段按照右端点进行由大到小排序,对于每个类维护后缀最小值(最远的左端点)
然后就可以枚举a b,进行快乐的二分了
#include<bits/stdc++.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#define fi l
#define se r
#define ll long long
#define ull unsigned long long
using namespace std;
const int N=1e6+5;
const ll md=1e9+7;
const ll inf=1e18;
ll n,a[N],b[N];
struct aa{
ll l,r;
};
bool cmp(aa x,aa y){
return x.r<y.r;
}
vector<aa>al,ar,bl,br;
ll sufal[N],sufar[N],sufbl[N],sufbr[N],all;
void solve(){
//fucking and strange
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
}
for(int i=1;i<=n;i++){
if(a[i]<=b[i]){
al.push_back({a[i],b[i]});
br.push_back({a[i],b[i]});
}else{
ar.push_back({b[i],a[i]});
bl.push_back({b[i],a[i]});
}
all+=abs(a[i]-b[i]);
}
sort(al.begin(),al.end(),cmp);
sort(ar.begin(),ar.end(),cmp);
sort(bl.begin(),bl.end(),cmp);
sort(br.begin(),br.end(),cmp);//这里写的比较冗余,赛时就怎么清楚怎么来了
ll del=0;
sufal[al.size()]=sufbl[bl.size()]=inf;
for(int i=al.size()-1;i>=0;i--){
sufal[i]=min(sufal[i+1],al[i].fi);
}
for(int i=bl.size()-1;i>=0;i--){
sufbl[i]=min(sufbl[i+1],bl[i].fi);
}
for(int i=0;i<ar.size();i++){
if(al.size()==0)break;
ll key=ar[i].se,l=0,r=al.size()-1,m;
// cout<<"key "<<key<<endl;
while(l<r){
m=(l+r)/2;
if(al[m].se<key){
l=m+1;
}else{
r=m;
}
}
// cout<<al[l].fi<<" "<<al[l].se<<" "<<sufal[l]<<endl;
if(al[l].se>=key){
del=max(del,min(ar[i].se-ar[i].fi,ar[i].se-sufal[l]));
}
}
for(int i=0;i<br.size();i++){
if(bl.size()==0)break;
ll key=br[i].se,l=0,r=bl.size()-1,m;
while(l<r){
m=(l+r)/2;
if(bl[m].se<key){
l=m+1;
}else{
r=m;
}
}
if(bl[l].se>=key){
del=max(del,min(br[i].se-br[i].fi,br[i].se-sufbl[l]));
}
}
// cout<<"del "<<del<<endl;
cout<<all-del*2<<endl;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
}
J.Roulette
题意:有一整数n,每次赌博可以支付 的代价 有的概率赢获得,的概率输损失。
如果第 i-1回合是获胜的那么 否则
求多赚m元的概率
思路:手玩一下就可以发现每个n到n+1的概率是独立的,内部的概率计算是一个等比数列的和。
#include<bits/stdc++.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
using namespace std;
const int N=0;
const ll md=998244353;
const ll inf=1e18;
ll n,m;
ll ksm(ll a,ll b){
ll res=1;
while(b){
if(b&1)res=res*a%md;
a=a*a%md;
b>>=1;
}
return res;
}
ll getinv(ll a,ll b){
return ksm(a,b-2);
}
ll getv(ll a1,ll q,ll n){//等比
return a1*((1+md-ksm(q,n))%md)%md* getinv((1+md-q)%md,md)%md;
}
void solve(){
//fucking and strange
cin>>n>>m;
ll inv=getinv(2,md);
ll now=0,be=n,win=1;
for(int i=0;i<60;i++){
now+=(1ll<<i);
if(now>=n+m){
now=n+m;
}
if(now>=be){
// cout<<now<<" "<<be<<" "<<i<<" "<<getv(inv,inv,i)<<endl;
win=win*ksm(getv(inv,inv,i)%md,(now-be))%md;
be=now;
}
if(now==n+m){
break;
}
}
cout<<win<<endl;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t=1;
while(t--){
solve();
}
}
K.Subdivision
题意:给定带有n个顶点和m条边的图,可以进行多次一下操作
选择一条边 ,由和去替换它,w为新加的点
找出与顶点1距离不超过k的最大顶点数
思路:与顶点1的最小距离,可以通过bfs从图重构出各个点与顶点最小的树。那么只要去填满深度小于等于k的位置就可以,每个点可以延申出多少条链去填充通过维护每个点的deg来判断。
注意1和叶子节点的特判.
#include<bits/stdc++.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
using namespace std;
const int N=1e5+5;
const ll md=998244353;
const ll inf=1e18;
int n,m,hd[N],cnte=0;
struct aa{
int v,nt;
}e[N<<2];
void add(int v,int u){
e[++cnte].v=u;
e[cnte].nt=hd[v];
hd[v]=cnte;
}
vector<int>son[N];
int vis[N],p[N];
ll k,deg[N],dep[N];
void bfs(int x){
queue<int>q;
q.push(x);
dep[x]=0;
// cout<<"bfs "<<endl;
for(int i=1;i<=n;i++){
p[i]=0;
}
p[1]=1;
while(!q.empty()){
int now=q.front();
q.pop();
if(vis[now])continue;
// cout<<now<<endl;
vis[now]=1;
for(int i=hd[now];i;i=e[i].nt){
int v=e[i].v;
if(!vis[v]){
if(!p[v]){
son[now].push_back(v);
// cout<<x<<" "<<v<<endl;
p[v]=now;
dep[v]=dep[now]+1;
}
q.push(v);
}
}
}
}
ll ans=0;
void dfs(int x){
if(dep[x]<=k){
ll ey=deg[x]-(x==1?0:1)-son[x].size();
if(x!=1&&ey==0&&son[x].size()==0)ey=1;
ans+=1;
// cout<<"dfs "<<x<<" "<<ey<<" "<<son[x].size()<<" "<<dep[x]<<endl;
ans+=ey*(k-dep[x]);
for(int v:son[x]){
dfs(v);
}
}
}
void solve(){
//fucking and strange
cin>>n>>m>>k;
for(int v,u,i=1;i<=m;i++){
cin>>v>>u;
add(v,u);
add(u,v);
deg[v]++;
deg[u]++;
}
bfs(1);
// for(int i=1;i<=n;i++){
// cout<<i<<" "<<dep[i]<<" "<<deg[i]<<endl;
// }
dfs(1);
cout<<ans<<endl;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
}
L.Three Permutations
中国剩余定理 待补
M.Water
题意:有A容量的杯子,B容量的杯子,确定能否通过以下操作确切的喝到x单位的水,如果能输出最小的操作数,否则输出-1
操作1:将两个瓶子中的一个装满水
操作2:将两个瓶子中的一个倒空
操作3:喝掉其中一个瓶子中的所有水
操作4:在不溢出的情况下,尽可能多地将水从一个瓶子转移到另一个瓶子。具体来说,如果两个瓶子分别含有a和b单位的水,他只能从A瓶向B瓶转移min(a,B−b)的水量,或者从B瓶向A瓶转移min(b,A−a)的水量。
题解:exgcd (赛时不会只能看着队友debug, 恼羞成怒,隔天去了解)
题解的证明,,,看不明白。
但是可以从题意里抽象出一个 的一个式子,我们的目的之一是求出这个式子。这个很简单,套一个exgcd的板子就可以。接下来就是去将得到的x y 转化成操作数量。
如果x为正数 (我觉得)可以看作 往杯子里到入新的水或者喝掉某个杯子里面的水。
如果x为负数 (我觉得)可以看作 倒空某个杯子里的水或 转移水
y同上
那么当且时 ,倒入新的水与喝水是两部操作故而 *2 下一种情况类似。
当 时 ,因为最后有容器的水我可以不倒掉,所以节约了一次操作 故而 -1。
#include<bits/stdc++.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
using namespace std;
const int N=0;
const ll md=1e9+7;
const ll inf=1e18;
const ll eps=1e-9;
const double E=2.718281828;
ll A,B,C;
ll exgcd(ll a,ll b,ll &x,ll &y){
if(!b){
x=1;
y=0;
return a;
}
ll g=exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-a/b*y;
return g;
}
ll getv(ll p,ll kx,ll x,ll ky,ll y){
x=kx*p+x;
y=ky*p+y;
if(x<0&&y<0){
return inf;
}
if(x*y>=0){
return 2*(x+y);
}else{
return 2*(abs(x)+abs(y))-1;
}
}
void solve(){
//fucking and strange
cin>>A>>B>>C;
ll x,y;
ll g=exgcd(A,B,x,y);
if(C%g){
cout<<-1<<"\n";
}else{
ll mul=C/g;
x*=mul;
y*=mul;
ll kx=B/g,ky=A/g*-1;
ll p[4];
p[1]=ceil(-1.0*x/kx),p[2]=floor(-1.0*y/ky);
ll ans=inf;
for(int i=1;i<=2;i++){
for(int j=-1;j<=1;j++){
ans=min(ans,getv(p[i]+j,kx,x,ky,y));
}
}
cout<<ans<<"\n";
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--){
solve();
}
}