【考试总结】2022-05-15
出题人
如果 那么可以让 ,剩下的
其实问题就是找到一个 的子集 使得存在 满足
这时候 注意这里的等式一定是全集构成一个整环而不存在枝杈,否则让基环森林里面的任意一个环满足即可
继续观察上面的等式发现:由于 全是奇数所以 是偶数,将 按照下标奇偶性分成两组,那么两组的和均为
那么现在问题就是在 中找到两个权值和大小都相同的子集,通过枚举子集的方式可以规避三进制,使用哈希表存储信息即可
卡常技巧包括但不限于将使用哈希表的一侧大小设为 或者当程序运行时间超过阀值输出 NO
Code Display
const int N=400;
int n,a[N],b[N],v[N];
pair<int,int> ans[N];
unordered_map<int,pair<int,int> > mp[N];
int sum1[1<<15],sum2[1<<17];
signed main(){
freopen("problemsetter.in","r",stdin); freopen("problemsetter.out","w",stdout);
n=read();
rep(i,1,n) a[i]=read();
rep(i,1,n) if(a[i]%2==0){
puts("Yes");
vector<int> b={a[i]/2};
vector<pair<int,int> > ans;
rep(j,1,n){
if(i==j) ans.emplace_back(1,1);
else{
b.emplace_back(a[j]-b[0]);
ans.emplace_back(1,b.size());
}
}
rep(e,0,n-1) print(b[e]); putchar('\n');
rep(e,0,n-1) print(ans[e].fir),print(ans[e].sec),putchar('\n');
exit(0);
}
if(n<=2) puts("No"),exit(0);
int n1=max(1ll,n/2-2),n2=n-n1;
int U1=1<<n1,U2=1<<n2;
rep(s,1,U1-1){
int lb=s&(-s),id=__builtin_ctzll(lb)+1;
sum1[s]=sum1[s^lb]+a[id];
}
rep(i,n1+1,n) b[i-n1]=a[i];
rep(s,1,U2-1){
int lb=s&(-s),id=__builtin_ctzll(lb)+1;
sum2[s]=sum2[s^lb]+b[id];
}
auto output=[&](const pair<int,int> S,const pair<int,int> T){
if(S.fir+S.sec+T.sec+T.fir==0) return ;
vector<pair<int,int> >Plu,Min;
for(int i=1;i<=n1;++i){
if(S.fir>>(i-1)&1) Plu.emplace_back(a[i],i);
if(S.sec>>(i-1)&1) Min.emplace_back(a[i],i);
}
for(int i=1;i<=n2;++i){
if(T.fir>>(i-1)&1) Plu.emplace_back(b[i],i+n1);
if(T.sec>>(i-1)&1) Min.emplace_back(b[i],i+n1);
}
sort(Plu.begin(),Plu.end());
sort(Min.begin(),Min.end());
int num=0,cur=0;
b[++num]=cur;
int siz=Plu.size();
while(Min.size()||Plu.size()){
if(Plu.size()==Min.size()){
cur=Plu.back().fir-cur;
ans[Plu.back().sec]={num,num+1};
Plu.pop_back();
}else{
cur=Min.back().fir-cur;
ans[Min.back().sec]={num,num==siz*2?1:num+1};
Min.pop_back();
}
if(num<siz*2) b[++num]=cur;
}
for(int i=1;i<=n;++i) if(!ans[i].fir){
ans[i]={1,++num};
b[num]=a[i]-b[1];
}
puts("Yes");
for(int i=1;i<=n;++i) print(b[i]); putchar('\n');
for(int i=1;i<=n;++i) print(ans[i].fir),print(ans[i].sec),putchar('\n');
exit(0);
};
rep(S,0,U1-1){
for(int T=S;;T=(T-1)&S){
int num=__builtin_popcount(T)-__builtin_popcount(S^T);
mp[n+num][sum1[T]-sum1[S^T]]=make_pair(T,S^T);
if(!T) break;
}
}
rep(S,0,U2-1){
for(int T=S;;T=(T-1)&S){
int num=__builtin_popcount(T)-__builtin_popcount(S^T);
if(mp[n-num].count(-sum2[T]+sum2[S^T])){
output(mp[n-num][-sum2[T]+sum2[S^T]],make_pair(T,S^T));
}
if(!T) break;
}
if(1.0*clock()/CLOCKS_PER_SEC>1.6) break;
}
puts("No");
return 0;
}
彩色挂饰
建立圆方树,设 表示 或 表示点双的根的颜色为 时最少的联通块数
对于实点 可以直接合并下辖虚点的信息即
对于虚点 需要对联通分量里面的异色联通块数进行计算:
预处理出来点双里面每个子集的导出子图形成的联通块数量,以及每个子集填入哪种合法的颜色形成的联通块最少,再进行一个枚举子集再枚举子集填哪种颜色的 来计算贡献
常见的建立圆方树的过程就能让父亲作为方点出边 vector
中的第一个点,要注意添加子集的过程中强制让包含 元素的子集填成所需颜色
注意这样的统计方式每个方点的父亲会被算两次,但是根这个实点只会被算一次,需要根据实际含义再加加减减
Code Display
const int N=2e5+10,inf=2e5+10;
int n,m,k,s;
vector<int> G[N],g[N];
int dp[N][25];
int col[N][64],con[N][64];
int dfn[N],low[N],stk[N],top,nds,tim;
int fa[N],ini[N];
unordered_set<int> edge[N];
bool exi[10][10];
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void tarjan(int x){
dfn[x]=low[x]=++tim; stk[++top]=x;
for(auto t:g[x]){
if(!dfn[t]){
tarjan(t); ckmin(low[x],low[t]);
if(low[t]>=dfn[x]){
++nds;
G[nds].emplace_back(x);
G[x].emplace_back(nds);
do{
G[nds].emplace_back(stk[top]);
G[stk[top]].emplace_back(nds);
--top;
}while(stk[top+1]!=t);
int siz=G[nds].size(),U=1<<siz;
for(int i=0;i<siz;++i){
for(int j=i+1;j<siz;++j){
exi[i][j]=exi[j][i]=edge[G[nds][i]].find(G[nds][j])!=edge[G[nds][i]].end();
}
}
for(int i=0;i<U;++i){
for(int j=0;j<siz;++j) if(i>>j&1){
int t=G[nds][j];
fa[j]=j;
if(ini[t]){
if(!col[nds][i]) col[nds][i]=ini[t];
else if(col[nds][i]!=ini[t]) col[nds][i]=-1;
}
}
for(int j=0;j<siz;++j) if(i>>j&1){
for(int k=j+1;k<siz;++k) if(i>>k&1){
if(exi[j][k]) fa[find(j)]=find(k);
}
}
for(int j=0;j<siz;++j) if(i>>j&1) con[nds][i]+=fa[j]==j;
}
}
}else ckmin(low[x],dfn[t]);
}
return ;
}
inline int solve(int x,int fat,int c){
if(~dp[x][c]) return dp[x][c];
if(x<=n){
int sum=0;
for(auto t:G[x]) if(t!=fat) sum+=solve(t,x,c);
return dp[x][c]=sum;
}
if(ini[fat]&&ini[fat]!=c) return dp[x][c]=inf;
int num=G[x].size(),U=1<<num; --U;
int dp1[64],dp2[64][25];
rep(i,0,k) dp2[0][i]=0;
for(int i=1;i<=U;++i){
dp2[i][0]=inf;
int lb=i&(-i),id=__builtin_ctzll(lb);
for(int c=1;c<=k;++c){
if(G[x][id]==fat) dp2[i][c]=dp2[i^lb][c];
else dp2[i][c]=dp2[i^lb][c]+solve(G[x][id],x,c);
ckmin(dp2[i][0],dp2[i][c]);
}
}
dp1[0]=0;
for(int S=1;S<=U;++S){
dp1[S]=inf;
for(int T=S;T;T=(T-1)&S){
if(col[x][T]==-1) continue;
int cur=col[x][T];
if(T&1){
if(cur!=c&&cur) continue;
cur=c;
}
ckmin(dp1[S],dp1[S^T]+con[x][T]+dp2[T][cur]);
}
}
return dp[x][c]=dp1[U];
}
signed main(){
freopen("colorful.in","r",stdin); freopen("colorful.out","w",stdout);
nds=n=read(); m=read(); k=read(); read();
for(int i=1;i<=n;++i) ini[i]=read();
for(int i=1;i<=m;++i){
int u=read(),v=read();
edge[u].insert(v);
edge[v].insert(u);
g[u].emplace_back(v);
g[v].emplace_back(u);
}
for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
memset(dp,-1,sizeof(dp));
int val=inf;
if(ini[1]) val=solve(1,0,ini[1]);
else rep(i,1,k) ckmin(val,solve(1,0,i));
print(val-(nds-n)+1);
return 0;
}
逆转函数
预处理每个 的上一次下一次的出现位置 ,那么 存在逆转函数等价于 或者以下三个条件都满足:
-
存在逆转函数
-
或者
-
或者
注意到这种判定方式和回文串类似,而所求是每个 “类回文区间” 的 的和,用 来做,计算每个点作为回文中心时对答案的贡献
将 扩展成 时维护 以及新增的对答案的贡献是平凡的,但是在继承时存在两者取 的操作会让信息减少,那么先继承 的信息,再减至 mid+r[mid]
处,信息的减少和增加的处理方式一样
观察到如果保留最靠右的 的 则 是不断右移的,而扩展本身会让最靠右的回文串的右端点右移,那么复杂度就是 的
Code Display
const int N=6e5+10;
int a[N],pw[N],n,m;
int pre[N],lst[N],nxt[N];
int num[N],sum[N],r[N];
signed main(){
freopen("invfunc.in","r",stdin); freopen("invfunc.out","w",stdout);
n=read(); m=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=n;i>=1;--i) a[i<<1]=a[i];
rep(i,1,n*2+1) if(i&1) a[i]=m+1;
pw[0]=1;
for(int i=1;i<=n*2+1;++i){
pw[i]=mul(pw[i-1],m);
pre[i]=lst[a[i]];
lst[a[i]]=i;
}
for(int i=1;i<=m+1;++i) lst[i]=n*2+2;
for(int i=n*2+1;i>=1;--i){
nxt[i]=lst[a[i]];
lst[a[i]]=i;
}
auto check=[&](int l,int r){
if(r-l<=1) return true;
if(!l||r>2*n+1) return false;
if(pre[r]>l&&a[l]!=a[l+r-pre[r]]) return false;
if(nxt[l]<r&&a[r]!=a[l+r-nxt[l]]) return false;
return true;
};
int ans=0,cen=num[1]=1;
for(int i=2;i<=n*2;++i){
if(i<=cen+r[cen]){
r[i]=r[2*cen-i];
sum[i]=sum[2*cen-i];
num[i]=num[2*cen-i];
while(i+r[i]>cen+r[cen]){
if((i+r[i])&1) ckdel(sum[i],pw[m+1-num[i]]);
int lef=cen*2-i-r[i],rig=cen*2-i+r[i];
if(nxt[lef]>rig) --num[i];
if(pre[rig]<lef+1) --num[i];
--r[i];
}
}else num[i]=1;
while(check(i-r[i]-1,i+r[i]+1)){
++r[i];
if(nxt[i-r[i]]>i+r[i]-1) ++num[i];
if(pre[i+r[i]]<i-r[i]) ++num[i];
if((i+r[i])&1) ckadd(sum[i],pw[m+1-num[i]]);
}
if(i+r[i]>=cen+r[cen]) cen=i;
ckadd(ans,sum[i]);
}
print(ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律