【考试总结】2022-03-08
开幕雷击:
我
把图用 化成 ,并计算每个强连通分量里面出现过的点的数量和强连通分量的大小
最直接的想法是使用一个最小链覆盖来求最小消耗,更简单的建图方式也能做
对于 之间的边,直接连 , 向 连 的大小, 向 连反向无穷边表示可以走多点路径, 向每个点连强连通分量里面出现过的字符数量
如果当前强连通分量是 大小大于 或者有出路 且每个点都是出现过的字符,那么 向 连出现过的字符数量 否则是本原数量
容易发现这样子的建图符合实际含义
Code Display
const int N=1e5+10,inf=0x3f3f3f3f;
struct Network_Flow{
struct edge{int to,nxt,lim;}e[N<<2];
int head[N],dep[N],S,T,ecnt=1,cur[N];
inline void adde(int u,int v,int w){
e[++ecnt]={v,head[u],w}; head[u]=ecnt;
return ;
}
inline void add(int u,int v,int w){return adde(u,v,w),adde(v,u,0);}
inline bool bfs(){
queue<int> q; q.push(S); rep(i,1,T) cur[i]=head[i],dep[i]=0; dep[S]=1;
while(q.size()){
int fr=q.front(); q.pop();
for(int i=head[fr];i;i=e[i].nxt) if(e[i].lim){
int t=e[i].to; if(dep[t]) continue;
dep[t]=dep[fr]+1; q.push(t);
}
} return dep[T];
}
inline int dfs(int x,int in){
if(x==T) return in; int out=0;
for(int i=head[x];i;cur[x]=i,i=e[i].nxt) if(e[i].lim){
int t=e[i].to; if(dep[t]!=dep[x]+1) continue;
int res=dfs(t,min(in,e[i].lim));
e[i].lim-=res; e[i^1].lim+=res;
in-=res; out+=res;
if(!in) break;
}
if(!out) dep[x]=0; return out;
}
inline int dinic(){
int sum=0;
while(bfs()) sum+=dfs(S,inf);
return sum;
}
inline void init(){
ecnt=1; S=62<<1|1; T=S+1;
rep(i,1,T) head[i]=0;
return ;
}
}F;
int n,num[100];
char s[N];
inline int decode(char s){
if(s>='a'&&s<='z') return s-'a'+1;
if(s>='A'&&s<='Z') return s-'A'+27;
return s-'0'+53;
}
vector<int> G[N];
int dfn[N],low[N],scc,bel[N],stk[N],siz[N],cnt[N],top,tim;
bool ins[N],out[N];
inline void tarjan(int x){
ins[stk[++top]=x]=1; dfn[x]=low[x]=++tim;
for(auto t:G[x]){
if(!dfn[t]) tarjan(t),ckmin(low[x],low[t]);
else if(ins[t]) ckmin(low[x],dfn[t]);
}
if(dfn[x]==low[x]){
++scc;
do{
cnt[scc]+=num[stk[top]];
siz[scc]++;
ins[stk[top]]=0;
bel[stk[top]]=scc;
}while(stk[top--]!=x);
} return ;
}
signed main(){
freopen("graph.in","r",stdin); freopen("graph.out","w",stdout);
int Test=read(); while(Test--){
scanf("%s",s+1); n=strlen(s+1);
rep(i,1,62){
num[i]=0;
bel[i]=dfn[i]=low[i]=siz[i]=cnt[i]=0;
ins[i]=out[i]=0;
G[i].clear();
}
top=tim=scc=0;
rep(i,1,n) num[decode(s[i])]=1;
F.init();
int m=read();
while(m--){
char lnk[10];
scanf("%s",lnk+1);
int v1=decode(lnk[1]),v2=decode(lnk[2]);
G[v1].push_back(v2);
}
rep(i,1,62) if(!dfn[i]) tarjan(i);
for(int i=1;i<=62;++i){
for(int t:G[i]) if(bel[t]!=bel[i]){
F.add(bel[i],bel[t]+scc,inf);
out[bel[i]]=1;
}
}
for(int i=1;i<=scc;++i){
if(cnt[i]) F.add(F.S,i,cnt[i]);
F.add(i+scc,i,inf);
//使走过多个 scc 可行
if(siz[i]==cnt[i]&&(cnt[i]>1||out[i])){
F.add(i,i+scc,cnt[i]-1);
//如果只有一个无出路的满点那么可以走掉
//如果多于一个点的满scc就必须带来损耗
}else F.add(i,i+scc,cnt[i]);
F.add(i+scc,F.T,siz[i]);// 空点也要放流
}
print(F.dinic());
}
return 0;
}
想不出
的斜率就是保证点发出的射线不经过另外一个整点,那么变化坐标轴可以让南偏东/西变成向下、向左
使用平面图欧拉公式 ,这里 是射线端点和交点个数之和 ,容易发现 恰好是二倍交点也就是 ,所以所求就是
对于具有左上右下关系的一对点 一定是 出发向下, 出发向左,否则调整过后更为优秀,类似地我们可以得出存在一条 上升的过程中单调不降的分界线满足分界线上面射线向下,分界线下面射线向左
找到分界线可以逐点判定,其实本质上是二次函数求最值的过程,那么如果这个点左上的点数大于右下的点数,那么射线朝左,否则朝上
最后模拟连边过程写个 连边的并查集即可,当然使用 优化建图可以低于这个复杂度
最后的问题是处理坐标轴变换,一种可能的方式是设 为新的 轴,那么 坐标数值大小关系可以通过 映射到 轴来比较
Code Display
const int N=2010;
int x[N],y[N],ans,n,d[N],xid[N],yid[N],mp[N],dir[N];
const double get_b(int x,int y,double k){return y-k*x;}
struct BIT{
int c[N];
inline void insert(int x,int v){for(;x<=n;x+=x&(-x)) c[x]+=v; return ;}
inline int query(int x){int res=0; for(;x;x-=x&(-x)) res+=c[x]; return res;}
inline int query(int l,int r){return query(r)-query(l-1);}
}suf,pre;
int per[N];
struct Dsu{
int fa[N];
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void init(){rep(i,1,n) fa[i]=i; return ;}
inline void merge(int x,int y){fa[find(x)]=find(y);}
}T;
signed main(){
freopen("surface.in","r",stdin); freopen("surface.out","w",stdout);
n=read();
rep(i,1,n) x[i]=read(),y[i]=read(),xid[i]=yid[i]=i;
const double K=sqrt(3);
sort(xid+1,xid+n+1,[&](const int a,const int b){
return get_b(x[a],y[a],-K)>get_b(x[b],y[b],-K);
});
sort(yid+1,yid+n+1,[&](const int a,const int b){
return get_b(x[a],y[a],K)>get_b(x[b],y[b],K);
});
for(int i=1;i<=n;++i) per[yid[i]]=i;
for(int i=1;i<=n;++i) mp[i]=per[xid[i]];
rep(i,1,n) suf.insert(i,1);
for(int i=1;i<=n;++i){
int down=suf.query(1,mp[i]-1),lef=pre.query(mp[i]+1,n);
suf.insert(mp[i],-1);
pre.insert(mp[i],1);
if(down<lef) dir[i]=1;//turn left
}
T.init();
for(int i=1;i<=n;++i) if(!dir[i]){
for(int j=i+1;j<=n;++j) if(dir[j]&&mp[i]>mp[j]) T.merge(i,j),++ans;
}
set<int> st;
rep(i,1,n) st.insert(T.find(i));
print(ans-n+st.size());
return 0;
}
题目名称
集合的 是
那么考虑找到答案的 ,考虑写答案作:
不难发现在选 个意义下只有上面 种算子是合法的
尝试根据实际含义来找到 的值,可能的选值种类集合有以下几种,需要让不合法的种类计算次数为 否则为
-
型,只在 里面算,由于外面带 的系数,所以要算 次,也就是
-
型:在每个部分都只会计算 次,所以限制
-
型:在 中表现为四选 之后 选 ;在 中 带走 之后 可以换顺序,两种方案,得到
-
型:
在 中表现为两组占四个
在 中是 算两次
在 中一组在 里面算,另外一组在 里面算,系数都是
所以有
-
型:
在 中是
在 中是 个
在 中是删掉 之后换顺序,两种方案
所以有
(是不是再推广一下就能到选任意个的容斥了)
方程组是满秩的,可以得到 的值。至此答案可以得出为
可以暴力计算 ,对于剩下两项需要使用一类折半搜索来解决
以 为先,计算出来 之后逐个枚举 ,那么要计算得到
最后一步直接将组合数展开作下降幂除阶乘的形式就能得到了,那么对 之外的项维护前缀和,每次查询 就可以找到 前缀和来得到答案
另外的 前置一个恒等式
证明
的实际含义是从 ,
, 的含义是在 里面选择一对点
对于和 同奇偶性的一对点 发现计算次数是 由于奇偶性相同, 而其他项消掉了,不同奇偶性的类似,正好全消掉了是
和上面的一样推推:
下取整的计算考虑继续拆成 的和以及 的值的和即可,前面可以直接拆成下面
后面分开 的奇偶性计算,查询找 的奇偶性减去再除 即可,这里使用 来做前缀和就只用维护一个了
Code Display
const int N=1010;
int n,s,l[N],r[N],ans;
signed main(){
freopen("count.in","r",stdin); freopen("count.out","w",stdout);
n=read(); s=read();
rep(i,1,n) l[i]=read(),r[i]=read();
if(s%4==0){
for(int i=1;i<=n;++i){
if(l[i]*4<=s&&s<=r[i]*4){
ckdel(ans,6);
break;
}
}
}
rep(i,1,n) rep(j,1,n){
int L=max(0ll,s-r[j]),R=s-l[j];
if(R<l[i]*3||L>r[i]*3) continue;
ckmax(L,l[i]*3); ckmin(R,r[i]*3);
while(L%3) ++L; while(R%3) --R;
if(R<L) continue;
ckadd(ans,mul(8,(R-L)/3+1));
}
vector<pair<int,int> >now,tmp;
rep(i,1,n) rep(j,1,n){
now.emplace_back(l[i]+l[j],1);
now.emplace_back(r[i]+r[j]+2,1);
now.emplace_back(r[i]+l[j]+1,mod-1);
now.emplace_back(r[j]+l[i]+1,mod-1);
}
sort(now.begin(),now.end());
for(auto t:now){
if(!tmp.size()||tmp.back().fir!=t.fir) tmp.push_back(t);
else ckadd(tmp.back().sec,t.sec);
}
now.clear();
for(auto t:tmp) if(t.sec) now.push_back(t);
if(s%2==0){
for(auto t:now) if(t.fir<=s/2){
int d=s/2-t.fir;
ckadd(ans,mul(3,(d+1)*t.sec%mod));
}
}
if(1){
vector<int> sum[4];
int siz=now.size();
rep(i,0,3) sum[i].resize(siz);
for(int i=0;i<siz;++i){
sum[0][i]=now[i].sec;
sum[1][i]=mul(now[i].sec,now[i].fir);
sum[2][i]=mul(now[i].sec,mul(now[i].fir,now[i].fir));
sum[3][i]=mul(now[i].sec,ksm(now[i].fir,3));
}
rep(i,1,siz-1) rep(j,0,3) ckadd(sum[j][i],sum[j][i-1]);
int contr=0,pter=siz-1;
for(auto t:now){
while(pter>=0&&now[pter].fir>s-t.fir) --pter;
if(pter<0) break;
int d=s-t.fir;
int tsum=0;
ckadd(tsum,mul(sum[0][pter],mul(mul(d+1,d+2),d+3)));
ckdel(tsum,mul(sum[1][pter],(3*d*d+12*d+11)%mod));
ckadd(tsum,mul(sum[2][pter],(3*d+6)%mod));
ckdel(tsum,sum[3][pter]);
ckadd(contr,mul(tsum,t.sec));
}
ckadd(ans,mul(ksm(6,mod-2),contr));
}
if(1){
vector<pair<int,int> >poly;
rep(i,1,n) poly.emplace_back(2*l[i],1),poly.emplace_back(2*r[i]+2,mod-1);
vector<int> sum[3];
int siz=poly.size();
rep(i,0,2) sum[i].resize(siz);
for(int i=0;i<siz;++i){
sum[0][i]=poly[i].sec;
sum[1][i]=mul(poly[i].sec,poly[i].fir);
sum[2][i]=mul(poly[i].sec,mul(poly[i].fir,poly[i].fir));
}
rep(i,1,siz-1) rep(j,0,2) ckadd(sum[j][i],sum[j][i-1]);
int pter=siz-1,contr=0;
for(auto t:now){
while(pter>=0&&poly[pter].fir>s-t.fir) --pter;
if(pter<0) break;
int d=s-t.fir,tsum=0;
ckadd(tsum,mul(sum[0][pter],(d+2)*(d+2)%mod));
ckdel(tsum,mul(2*(d+2)%mod,sum[1][pter]));
ckadd(tsum,sum[2][pter]);
if(d&1) ckdel(tsum,sum[0][pter]);
ckadd(contr,mul(tsum,t.sec));
}
ckdel(ans,mul(3*contr%mod,(mod+1)/2));
}
print(mul(ans,ksm(24,mod-2)));
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律