D
图论,思维
给一些边,至少要有这些边,其他边可以随便加,问能不能得到一条链。
首先所有点度数不能超过2,因为链上点度数只会为1或2。但这样还可能出现环,环上所有点度数也都是2,没超过2。所以再来个dfs,用搜索树的方式,对所有联通块判环,具体来说就是把每个连通块都当成树,如果搜索时有返祖边就是带环。如果最后没有环就是对的
void solve(void){
int n,m;
cin>>n>>m;
vi cnt(n+1);
vvi g(n+1);
rep(i,1,m){
int u,v;
cin>>u>>v;
cnt[u]++;
cnt[v]++;
g[u].push_back(v);
g[v].push_back(u);
}
rep(i,1,n){
if(cnt[i]>2){
cout<<"No";
return;
}
}
vi vis(n+1);
auto &&dfs=[&](auto &&dfs,int u,int f)->bool{
vis[u]=1;
for(int v:g[u]){
if(v==f)continue;
if(vis[v]){
return 1;
}
if(dfs(dfs,v,u)){
return 1;
}
}
return 0;
};
rep(i,1,n){
if(!vis[i]){
if(dfs(dfs,i,-1)){
cout<<"No";
return;
}
}
}
cout<<"Yes";
}
E
倍增思想,记搜
给一堆硬币,从小到大,每两个相邻的面额都可以是倍数关系。问买 x x x最少需要用多少个硬币,可以找零,但找零使用的硬币也计入。
首先这种一堆互为倍数的面额,显然可以凑出来任意金额,并且想让使用的面额最小,应从大到小使用面额,这类似于倍增lca往上跳的过程。如果不能找零,就每次都选不超过当前金额的最大的即可。
但是这里可以找零,所以每一步就有两种选择,一种是不找零,使用当前的最大的凑出不超过 x x x的,然后还剩下 x m o d a i x\mod a_i xmodai的钱,递归往下走,用更小的接着凑。也可以用当前面额凑出来刚好超过 x x x的,多出来的钱用更小的面额找零。这里可以注意到找零的过程,对答案的贡献也是一样的,都是使用硬币的个数,所以往下递归不用区分是买还是找零,拥有相同的子问题定义。
这样看起来有个问题:买和找零可能会多次变换,比如第一层是买,第二层是找零,第三层不知道前面是找零的,以为自己是买,也选择找零,又一次转换就又变成买了。换一种说法,最后的方案中 c 1 a 1 + c 2 a 2 + . . . + c n a n c_1a_1+c_2a_2+...+c_na_n c1a1+c2a2+...+cnan, c i c_i ci是使用的个数, a i a_i ai是面额, c c c这个序列的正负号可能多次变换,比如 1 , − 1 , − 1 , 1 , − 1 , − 1 1,-1,-1,1,-1,-1 1,−1,−1,1,−1,−1,这对吗?
这其实是对的,这相当于所有系数是正的面额都用来买,系数是负的面额都用来找零。
最后就是个每次有两个分支的dfs,需要记忆化。
void solve(void){
int n,x;
cin>>n>>x;
vi a(n+1);
rep(i,1,n){
cin>>a[n-i+1];
}
map<int,map<int,int>>dp;
auto &&dfs=[&](auto &&dfs,int x,int i)->int{
if(i==n+1){
return x;
}
if(dp[x][i]){
return dp[x][i];
}
int res=1e18;
res=min(res,x/a[i]+dfs(dfs,x%a[i],i+1));
res=min(res,x/a[i]+1+dfs(dfs,a[i]*(x/a[i]+1)-x,i+1));
return dp[x][i]=res;
};
cout<<dfs(dfs,x,1);
}
F
二位数点,排序
每种礼物在ab两个人眼里有不同的价值,现在要从n种礼物中给ab两个人送礼,两个人送的可以一样也可以不一样。但如果a眼中认为b得到的礼物比他的更宝贵,就会打架,对b来说也是同理。问不打架的方案数
设a眼中的价值为 a i a_i ai,b眼中价值为 b i b_i bi,这可以转化为,如果送a的是第 i i i个,那么求 a j < = a i , b j > = b i a_j<=a_i,b_j>=b_i aj<=ai,bj>=bi的 j j j的个数?
很显然的二维数点,先对一个维度排序,满足这个维度的要求,然后用支持区间查的数据结构维护另一个维度,每次查询更大的元素个数。
有一些细节,首先是 a i a_i ai相等 b i b_i bi不相等的情况,比如 ( 2 , 5 ) ( 2 , 10 ) (2,5)(2,10) (2,5)(2,10),显然是可以构成一组答案的,但只有在访问顺序是 ( 2 , 10 ) , ( 2 , 5 ) (2,10),(2,5) (2,10),(2,5)的时候才能计入答案,因此我们需要对第一维度升序,第二维度降序。
其次对于完全相同的两个元素,他们可以构成两组方案,但一般的扫描线只会只会计算一次,因此需要手动把另一半补上。举例来说有序号为 1 , 2 , 3 1,2,3 1,2,3三个完全相同的礼物,扫描线只会计算上 ( 1 , 1 ) ( 1 , 2 ) ( 1 , 3 ) ( 2 , 2 ) ( 2 , 3 ) ( 3 , 3 ) (1,1)(1,2)(1,3)(2,2)(2,3)(3,3) (1,1)(1,2)(1,3)(2,2)(2,3)(3,3)这几个方案,但实际上 ( 2 , 1 ) ( 3 , 1 ) ( 3 , 2 ) (2,1)(3,1)(3,2) (2,1)(3,1)(3,2)也是合法的,假设有 n n n个相同的,有 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1)个方案被漏算了,把他们手动补上即可
值域很大,需要离散化,这里使用set排序+挨个赋值的做法
struct Tree{
#define ls u<<1
#define rs u<<1|1
struct Node{
int l,r;
ll mx,add;
}tr[N<<2];
void pushup(int u){
tr[u].mx=tr[ls].mx+tr[rs].mx;
}
void pushdown(int u){
if(tr[u].add){
tr[ls].mx+=tr[u].add*(tr[ls].r-tr[ls].l+1);
tr[rs].mx+=tr[u].add*(tr[rs].r-tr[rs].l+1);
tr[ls].add+=tr[u].add;
tr[rs].add+=tr[u].add;
tr[u].add=0;
}
}
void build(int u,int l,int r){
tr[u]={l,r,0,0};
if(l==r){
tr[u].mx=0;
return;
}
int mid=(l+r)>>1;
build(ls,l,mid); build(rs,mid+1,r);
pushup(u);
}
void modify(int u,int l,int r,int val){
if(tr[u].l>=l&&tr[u].r<=r){
tr[u].mx+=val*(tr[u].r-tr[u].l+1);
tr[u].add+=val;
return ;
}
else{
int mid=(tr[u].l+tr[u].r)>>1;
pushdown(u);
if(mid>=l) modify(ls,l,r,val);
if(r>mid) modify(rs,l,r,val);
pushup(u);
}
}
ll query(int u,int l,int r){
if(l<=tr[u].l&&tr[u].r<=r) return tr[u].mx;
pushdown(u);
int mid=(tr[u].l+tr[u].r)>>1;
if(r<=mid)return query(ls,l,r);
if(l>mid)return query(rs,l,r);
return query(ls,l,r)+query(rs,l, r);
}
}t;
void solve(void){
int n;
cin>>n;
vi a(n+1),b(n+1);
vvi c;
rep(i,1,n){
cin>>a[i];
}
rep(i,1,n){
cin>>b[i];
}
set<int>s;
map<int,int>mp;
int cur=0;
rep(i,1,n){
s.insert(a[i]);
}
for(int x:s){
mp[x]=++cur;
}
rep(i,1,n){
a[i]=mp[a[i]];
}
s.clear();
mp.clear();
cur=0;
rep(i,1,n){
s.insert(b[i]);
}
for(int x:s){
mp[x]=++cur;
}
rep(i,1,n){
b[i]=mp[b[i]];
}
map<pii,int>mp1;
rep(i,1,n){
mp1[{a[i],b[i]}]++;
c.push_back({a[i],b[i]});
}
sort(c.begin(),c.end(),[](vi &a,vi &b){
if(a[0]==b[0])return a[1]>b[1];
return a[0]<b[0];
});
int ans=0;
for(auto &[k,v]:mp1){
ans+=(v-1)*v/2;
}
int mx=s.size();
t.build(1,1,mx);
rep(i,0,n-1){
int x=c[i][1];
t.modify(1,x,x,1);
ans+=t.query(1,x,mx);
}
cout<<ans;
}