杂题 Solution 速通 #1
[ICPC2021 Nanjing R] Ancient Magic Circle in Teyvat
设 \(f_x\) 表示钦定至少有 \(x\) 条边的四元子图个数,由容斥我们有一条边都没有的子图个数是 \(f_0-f_1+f_2-f_3+f_4-f_5+f_6\),而所有边都有的个数就是 \(f_6\)。作差之后只需要求 \(f_0,f_1,f_2,f_3,f_4,f_5\)。
子图计数只要不是数 \(K_4\),其它所有的四元图都是好做的,把你会的三元环、四元环、\(K_4\) 少一条边的计数方法全用上就可以了。
#include <cstdio>
#include <vector>
using namespace std;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=x*10+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
const int N=100003,M=200003;
typedef long long ll;
typedef __int128 lll;
int n,m;lll res;
void write(lll x){if(x>9) write(x/10);putchar((x%10)^48);}
lll C4(int x){return (lll)x*(x-1)*(x-2)*(x-3)/24;}
lll C3(int x){return (lll)x*(x-1)*(x-2)/6;}
lll C2(int x){return (lll)x*(x-1)>>1;}
int eu[M],ev[M],d[N];
vector<int> adj[N],vec[N],ec[N];
int vis[N],ps[N];
inline bool cmp(int x,int y){
if(d[x]!=d[y]) return d[x]>d[y];
return x<y;
}
int main(){
n=read();m=read();
for(int i=1;i<=m;++i){
eu[i]=read();ev[i]=read();
adj[eu[i]].emplace_back(ev[i]);
adj[ev[i]].emplace_back(eu[i]);
}
for(int i=1;i<=n;++i) d[i]=adj[i].size();
for(int i=1;i<=m;++i)
if(cmp(eu[i],ev[i])) vec[eu[i]].emplace_back(ev[i]);
else vec[ev[i]].emplace_back(eu[i]);
for(int i=1;i<=n;++i) ec[i].resize(vec[i].size());
res=C4(n)-m*C2(n-2);
lll tmp=0;
for(int i=1;i<=m;++i){
tmp+=(lll)(d[eu[i]]+d[ev[i]]-2)*(n-4);
res-=(lll)(d[eu[i]]-1)*(d[ev[i]]-1);
}
res+=C2(m)+(tmp>>1);
for(int x=1;x<=n;++x){
res-=C3(d[x]);
int id=0;
for(int y:vec[x]) vis[y]=x,ps[y]=id++;
id=0;
for(int y:vec[x]){
int nid=0;
for(int z:vec[y]){
if(vis[z]==x){
res+=d[x]+d[y]+d[z]-n;
res-=ec[x][id]++;
res-=ec[x][ps[z]]++;
res-=ec[y][nid]++;
}
++nid;
}
++id;
}
}
for(int x=1;x<=n;++x){
for(int y:vec[x])
for(int z:adj[y]) vis[z]=0;
for(int y:vec[x])
for(int z:adj[y]) if(cmp(x,z)) res+=vis[z]++;
}
if(res<0) res=-res;
write(res);putchar('\n');
return 0;
}
[GDCPC2023] Classic Problem
套路题,离散化之后 Boruvka 一下就行了。
我为啥这玩意需要写拍才能调出来啊?
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define fi first
#define se second
typedef pair<int,int> pii;
typedef long long ll;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=x*10+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
const int N=100003,M=400013;
const int INF=0x3f3f3f3f;
int n,m;ll res;
int eu[N],ev[N],ew[N];
int buc[M],rk;
vector<pii> vec[M];
vector<int> arr[M];
int f[M],dir[M],mn[M];
int rt(int x){
if(f[x]==x) return x;
return f[x]=rt(f[x]);
}
int dis(int x,int y){
if(x>y) swap(x,y);
return buc[y-1]+1-buc[x];
}
int pre[M],suf[M],cur[M];
void solve(){
n=read();m=read();rk=0;res=0;
for(int i=1;i<=m;++i){
buc[++rk]=eu[i]=read();if(eu[i]>1) buc[++rk]=eu[i]-1;
buc[++rk]=ev[i]=read();if(ev[i]>1) buc[++rk]=ev[i]-1;
ew[i]=read();
}
buc[++rk]=n;
sort(buc+1,buc+rk+1);rk=unique(buc+1,buc+rk+1)-buc-1;
n=rk;
for(int i=1;i<=n;++i) vec[i].clear(),f[i]=i,res+=buc[i]-buc[i-1]-1;
for(int i=1;i<=m;++i){
eu[i]=lower_bound(buc+1,buc+n+1,eu[i])-buc;
ev[i]=lower_bound(buc+1,buc+n+1,ev[i])-buc;
vec[eu[i]].emplace_back(ev[i],ew[i]);
vec[ev[i]].emplace_back(eu[i],ew[i]);
}
int cnt=n-1;
while(cnt){
for(int i=1;i<=n;++i) arr[rt(i)].emplace_back(i),cur[i]=-1;
for(int i=1;i<=n;++i){
dir[i]=-1;mn[i]=INF;
if(f[i]==i){
int sz=arr[i].size();
for(int t=0;t<sz;++t){
int x=arr[i][t];
if(t&&arr[i][t-1]==x-1) pre[x]=pre[x-1];
else pre[x]=x-1;
}
for(int t=sz-1;~t;--t){
int x=arr[i][t];
if(t+1<sz&&arr[i][t+1]==x+1) suf[x]=suf[x+1];
else suf[x]=x+1;
}
for(int x:arr[i]){
for(auto [y,w]:vec[x]){
if(rt(y)!=i&&w<mn[i]) dir[i]=y,mn[i]=w;
cur[y]=x;
}
int t;
t=x-1;
while(t){
if(cur[t]==x){--t;continue;}
if(rt(t)==i){t=pre[t];continue;}
if(dis(x,t)<mn[i]) mn[i]=dis(x,t),dir[i]=t;
break;
}
t=x+1;
while(t<=n){
if(cur[t]==x){++t;continue;}
if(rt(t)==i){t=suf[t];continue;}
if(dis(x,t)<mn[i]) mn[i]=dis(x,t),dir[i]=t;
break;
}
}
}
}
for(int i=1;i<=n;++i){
arr[i].clear();
if((~dir[i])&&rt(i)!=rt(dir[i])) f[rt(i)]=rt(dir[i]),res+=mn[i],--cnt;
}
}
printf("%lld\n",res);
}
int main(){
int tc=read();
while(tc--) solve();
return 0;
}
[JOISC2017] Sparklers
很喜欢的一道牛题啊!但是时间问题我们还是速通 Solution。
考虑策略是两个人一但碰面了就不会分开,这样两个人碰面相当于合并成一个人,燃烧时间 \(+T\)。
二分速度 \(v\)。就变成了两个队列,每次可以花费距离的代价买下一个队头,然后赚 \(2Tv\) 的距离,问能否删空队列。
考虑经典的合并策略贪心,如果一个亏的策略下一个策略是赚的策略,那么显然在中间插入另一个队列的策略不优。所以合并这些策略后会形成前面都是赚后面都是亏的两个策略队列。
接下来一步很神。考虑赚的两段前缀一定先取完,而且能取则取。亏的两段后缀呢?考虑倒着做,最终花费代价恒定,所以说最终状态确定,这样倒推的时候也是能取则取。只需要判断一下正着倒着能否取完就行了。
#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=x*10+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
int n,k,t;
const int N=100003;
typedef long long ll;
int a[N];
struct node{
ll x,d;
friend node operator+(const node u,const node v){
return (node){max(u.x,v.x-u.d),u.d+v.d};
}
}s1[N],s2[N];
int n1,n2;
bool check(int w){
ll sum=(ll)n*w-(a[n]-a[1]);
if(sum<0) return 0;
n1=0;
for(int i=k;i>1;--i){
int d=a[i]-a[i-1];
node cur({d,w-d});
while(cur.d>=0&&n1&&s1[n1].d<0) cur=s1[n1--]+cur;
s1[++n1]=cur;
}
n2=0;
for(int i=k;i<n;++i){
int d=a[i+1]-a[i];
node cur({d,w-d});
while(cur.d>=0&&n2&&s2[n2].d<0) cur=s2[n2--]+cur;
s2[++n2]=cur;
}
bool fl=1;
while(fl){
fl=0;
while(n1&&s1[n1].d<0&&sum-s1[n1].d>=s1[n1].x) sum-=s1[n1--].d,fl=1;
while(n2&&s2[n2].d<0&&sum-s2[n2].d>=s2[n2].x) sum-=s2[n2--].d,fl=1;
}
int p1=1,p2=1;
fl=1;sum=w;
while(fl){
fl=0;
while(p1<=n1&&s1[p1].d>=0&&sum>=s1[p1].x) sum+=s1[p1++].d,fl=1;
while(p2<=n2&&s2[p2].d>=0&&sum>=s2[p2].x) sum+=s2[p2++].d,fl=1;
}
return p1>n1&&p2>n2;
}
int main(){
n=read();k=read();t=read();
for(int i=1;i<=n;++i) a[i]=read();
int l=0,r=ceil(0.5e9/t);
while(l<r){
int mid=(l+r)>>1;
if(check(2*mid*t)) r=mid;
else l=mid+1;
}
printf("%d\n",r);
return 0;
}
[ICPC2021 Macao R] the Matching System
什么鬼畜打表题。看看题目给的啥限制?完了这看起来完全规划不了啊!
进行一个表的打,发现取到最大值的它就是很有规律:
**********0*
011111111111
000000******
000000000000
原理都很好懂,比如说最大匹配的串一定那堆通配符匹配长度都是 0,所以耗费能量极多。
下面这个能量好算,在平方级别;上面这个能量通过一大堆插板法反复计算+竖行和公式得到 \({2n-1\choose n+1}+{2n-3\choose n-2}\),做完了。
#include <cstdio>
using namespace std;
const int P=1000000007;
const int N=10003;
typedef long long ll;
int qp(int a,int b=P-2){
int res=1;
while(b){
if(b&1) res=(ll)res*a%P;
a=(ll)a*a%P;b>>=1;
}
return res;
}
int fac[N],fiv[N],n;
int main(){
scanf("%d",&n);
if(n==1) puts("0\n0\n1");
else if(n==2) puts("*0\n00\n3");
else{
fac[0]=1;
for(int i=1;i<=2*n;++i) fac[i]=(ll)fac[i-1]*i%P;
fiv[2*n]=qp(fac[2*n]);
for(int i=2*n;i;--i) fiv[i-1]=(ll)fiv[i]*i%P;
int cur=((ll)fac[2*n-1]*fiv[n+1]+(ll)fac[2*n-3]*fiv[n-1])%P*fiv[n-2]%P;
for(int i=1;i<=n;++i) if(i==n-1) putchar('0');else putchar('*');
putchar('\n');
for(int i=1;i<=n;++i) if(i==1) putchar('0');else putchar('1');
putchar('\n');
printf("%d\n",cur);
}
int t=((n+1)>>1);
int res=(t+3)*t;
if(n&1) res-=t+1;
for(int i=1;i<=n;++i) if(i<=t) putchar('*');else putchar('0');
putchar('\n');
for(int i=1;i<=n;++i) putchar('0');
putchar('\n');
printf("%d\n",res);
return 0;
}
[ICPC2021 Macao R] Colorful Tree
知道套路就会了,但是这个套路还是很妙的!
考虑点集直径问题,这个东西是可以快速做信息合并的。而这道题要求端点颜色不同,其实是求两个点集之间的最远点对,由点集直径的结论我们知道只需要考虑两个点集直径的四个端点之间的最大距离即可,那么这个信息也是可以快速合并的。
信息不可减,所以用多颗线段树维护每个颜色的点集直径问题,再开一颗线段树处理两个不同颜色之间的最远点对。复杂度大常数 \(O(n\log n)\)。
#include <cstdio>
#include <vector>
#include <cassert>
#include <algorithm>
#define IL inline
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=x*10+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
const int N=500003;
typedef long long ll;
int q,n;
int hd[N],ver[N],nxt[N],val[N],tot;
IL void add(int u,int v,int w){
nxt[++tot]=hd[u];hd[u]=tot;ver[tot]=v;val[tot]=w;
}
IL void chmx(ll &x,ll v){if(x<v) x=v;}
int c[N],ps[N],sz[N],id[N];
int qx[N],qc[N];
int dfn[N],f[19][N],num;ll de[N];
IL int cmp(int u,int v){return dfn[u]<dfn[v]?u:v;}
IL int lca(int u,int v){
if(u==v) return u;
if((u=dfn[u])>(v=dfn[v])) swap(u,v);
int k=__lg(v-u++);
return cmp(f[k][u],f[k][v-(1<<k)+1]);
}
IL ll dis(int u,int v){return de[u]+de[v]-2*de[lca(u,v)];}
ll cur;
struct Info{
int u,v;ll d;
Info(int X=0):u(X),v(X),d(0){}
Info(int U,int V,ll D):u(U),v(V),d(D){}
friend bool operator<(const Info a,const Info b){return a.d<b.d;}
friend Info operator+(const Info a,const Info b){
cur=0;
if(!a.u&&!a.v) return b;
if(!b.u&&!b.v) return a;
Info c=max(a,b);
ll d;
chmx(cur,d=dis(a.u,b.u));if(d>c.d) c=Info(a.u,b.u,d);
chmx(cur,d=dis(a.u,b.v));if(d>c.d) c=Info(a.u,b.v,d);
chmx(cur,d=dis(a.v,b.u));if(d>c.d) c=Info(a.v,b.u,d);
chmx(cur,d=dis(a.v,b.v));if(d>c.d) c=Info(a.v,b.v,d);
return c;
}
}s[N<<2];
void dfs(int u,int fa){
f[0][dfn[u]=++num]=fa;
for(int i=hd[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa) continue;
de[v]=de[u]+val[i];
dfs(v,u);
}
}
struct SGT{
vector<Info> sg;
void init(int len){sg=vector<Info>(((len+1)<<2));}
void update(int x,int v,int p,int l,int r){
if(l==r){sg[p]=v;return;}
int mid=(l+r)>>1;
if(x<=mid) update(x,v,lc,l,mid);
else update(x,v,rc,mid+1,r);
sg[p]=sg[lc]+sg[rc];
}
}DS[N];
ll mx[N<<2];
void build(int p=1,int l=1,int r=q){
mx[p]=0;s[p]=Info();
if(l==r) return;
int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
}
void update(int x,Info v,int p=1,int l=1,int r=q){
if(l==r){s[p]=v;return;}
int mid=(l+r)>>1;
if(x<=mid) update(x,v,lc,l,mid);
else update(x,v,rc,mid+1,r);
s[p]=s[lc]+s[rc];
mx[p]=max({mx[lc],mx[rc],cur});
}
int main(){
int tc=read();
while(tc--){
q=read();++sz[c[n=1]=read()];num=0;
for(int i=1;i<=q;++i){
int op=read();
if(op){
int x=read(),c=read();
qx[i]=x;++sz[qc[i]=c];
}
else{
int ft=read();
++sz[c[++n]=read()];
add(ft,n,read());
qx[i]=n;qc[i]=0;
}
}
dfs(1,0);
for(int t=1;t<19;++t)
for(int i=1;i+(1<<t)-1<=n;++i)
f[t][i]=cmp(f[t-1][i],f[t-1][i+(1<<(t-1))]);
for(int i=1;i<=q;++i) DS[i].init(sz[i]);
build();
DS[c[1]].update(id[1]=++ps[c[1]],1,1,1,sz[c[1]]);update(c[1],DS[c[1]].sg[1]);
for(int i=1;i<=q;++i){
int x=qx[i];
if(qc[i]){
DS[c[x]].update(id[x],0,1,1,sz[c[x]]);update(c[x],DS[c[x]].sg[1]);
c[x]=qc[i];
}
DS[c[x]].update(id[x]=++ps[c[x]],x,1,1,sz[c[x]]);update(c[x],DS[c[x]].sg[1]);
printf("%lld\n",mx[1]);
}
for(int i=1;i<=q;++i) sz[i]=ps[i]=0;
for(int i=1;i<=n;++i) id[i]=c[i]=hd[i]=0;
while(tot) ver[tot]=nxt[tot]=0,--tot;
}
return 0;
}

浙公网安备 33010602011771号