补题
1. 1303F
大意: 给定$n\times m$矩阵, 初始全$0$, 若相邻两格元素相同, 那么它们连通. 每次操作修改元素的值, 求矩阵中连通块的个数.
因为颜色比较少, 可以分别考虑每种颜色, 那么修改操作就等价于删点和添点. 添点用并查集很容易维护, 删点倒序处理即可.
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int dx[]={0,0,-1,1}; const int dy[]={-1,1,0,0}; const int N = 2e6+10; int n,m,q,a[350][350]; struct _ { int x,y,id; }; vector<_> del[N],add[N]; int fa[N],ans[N]; int Find(int x) {return fa[x]?fa[x]=Find(fa[x]):x;} int ID(int x, int y) {return (x-1)*m+y;} void solve(vector<_> &v, int c) { REP(i,1,n) REP(j,1,m) a[i][j] = 0; REP(i,0,n*m) fa[i] = 0; for (auto t:v) { a[t.x][t.y] = 1; int w = 1; REP(i,0,3) { int x=t.x+dx[i],y=t.y+dy[i]; if (1<=x&&x<=n&&1<=y&&y<=m&&a[x][y]) { int u=Find(ID(t.x,t.y)),v=Find(ID(x,y)); if (u!=v) --w,fa[u]=v; } } ans[t.id] += w*c; } } int main() { scanf("%d%d%d",&n,&m,&q); int mx = 1; REP(i,1,q) { int x,y,c; scanf("%d%d%d",&x,&y,&c); if (a[x][y]==c) continue; mx = c; add[c].pb({x,y,i}); del[a[x][y]].pb({x,y,i}); a[x][y] = c; } REP(i,1,n) REP(j,1,m) del[a[i][j]].pb({i,j,0}); REP(i,0,mx) reverse(del[i].begin(),del[i].end()); REP(i,0,mx) if (add[i].size()) solve(add[i],1); REP(i,0,mx) if (del[i].size()) solve(del[i],-1); int now = 1; REP(i,1,q) { now += ans[i]; printf("%d\n",now); } }
2. 1301E
大意: 给定$n\times m$的格子, 每次操作求一个矩形内最大的合法商标面积.
二分答案+二维RMQ即可.
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 555; int n,m,q; char s[N][N]; int R[N][N],G[N][N],Y[N][N],B[N][N]; int Log[N],f[11][11][N][N]; void init() { Log[0] = -1; REP(i,1,N-1) Log[i]=Log[i>>1]+1; REP(k,1,10) for (int i=1;i+(1<<k-1)<=n;++i) { REP(j,1,m) { f[k][0][i][j] = max(f[k-1][0][i][j],f[k-1][0][i+(1<<k-1)][j]); } } REP(l,1,10) REP(k,0,10) { for (int i=1;i+(k?(1<<k-1):0)<=n;++i) { for (int j=1;j+(1<<l-1)<=m;++j) { f[k][l][i][j] = max(f[k][l-1][i][j],f[k][l-1][i][j+(1<<l-1)]); } } } } int RMQ(int x1, int y1, int x2, int y2) { if (x2<x1||y2<y1||x1<=0||y1<=0||x2>n||y2>m) return 0; int u = Log[x2-x1+1], v = Log[y2-y1+1]; x2 += -(1<<u)+1, y2 += -(1<<v)+1; auto A = f[u][v]; return max({A[x1][y1],A[x1][y2],A[x2][y1],A[x2][y2]}); } int main() { scanf("%d%d%d",&n,&m,&q); REP(i,1,n) scanf("%s",s[i]+1); REP(i,1,n) REP(j,1,m) if (s[i][j]=='R') { R[i][j] = min({R[i-1][j],R[i][j-1],R[i-1][j-1]})+1; } REP(i,1,n) PER(j,1,m) if (s[i][j]=='G') { G[i][j] = min({G[i-1][j],G[i][j+1],G[i-1][j+1]})+1; } PER(i,1,n) REP(j,1,m) if (s[i][j]=='Y') { Y[i][j] = min({Y[i+1][j],Y[i][j-1],Y[i+1][j-1]})+1; } PER(i,1,n) PER(j,1,m) if (s[i][j]=='B') { B[i][j] = min({B[i+1][j],B[i][j+1],B[i+1][j+1]})+1; } REP(i,1,n) REP(j,1,m) if (s[i][j]=='R') { f[0][0][i][j] = min({R[i][j],G[i][j+1],Y[i+1][j],B[i+1][j+1]}); } init(); while (q--) { int x1,y1,x2,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); int l=1,r=m/2,ans=0; while (l<=r) { if (RMQ(x1+mid-1,y1+mid-1,x2-mid,y2-mid)>=mid) ans=mid,l=mid+1; else r=mid-1; } printf("%d\n",4*ans*ans); } }
3. 1301F
大意: 给定$n\times m$的棋盘, 每一步可以走相邻的格子,或相同颜色的格子. $q$次询问,求两点之间最短路.
假设只走相邻格, 那么答案就是两点间的曼哈顿距离. 否则至少走一次同色格, 并且每种同色格之间最多走一次.
对每种颜色建一个虚点, 向对应颜色的格子连一条$0$边, 显然最短路中每个虚点最多经过$1$次.
预处理出每个虚点到每个格子的最短路, 那么最终答案就是虚点到起点最短路+虚点到终点最短路+1
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int dx[]={0,0,1,-1}; const int dy[]={1,-1,0,0}; const int N = 1e3+10, K = 50; int n,m,k,a[N][N],d[K][N][N],vis[K]; vector<pii> g[K]; queue<pii> q; void bfs(int x) { memset(d[x],0x3f,sizeof d[x]); memset(vis,0,sizeof vis); for (auto &t:g[x]) { d[x][t.x][t.y] = 0; q.push(t); } while (q.size()) { pii u = q.front(); q.pop(); int c = a[u.x][u.y], w = d[x][u.x][u.y]+1; if (!vis[c]) { vis[c] = 1; for (auto &v:g[c]) { if (d[x][v.x][v.y]>w) { d[x][v.x][v.y]=w; q.push(v); } } } REP(i,0,3) { pii v(u.x+dx[i],u.y+dy[i]); if (1<=v.x&&v.x<=n&&1<=v.y&&v.y<=m) { if (d[x][v.x][v.y]>w) { d[x][v.x][v.y]=w; q.push(v); } } } } } int main() { scanf("%d%d%d",&n,&m,&k); REP(i,1,n) REP(j,1,m) { scanf("%d",a[i]+j); g[a[i][j]].pb(pii(i,j)); } REP(i,1,k) bfs(i); int q; scanf("%d",&q); while (q--) { int x1,y1,x2,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); int ans = abs(x2-x1)+abs(y2-y1); REP(i,1,k) ans = min(ans, d[i][x1][y1]+d[i][x2][y2]+1); printf("%d\n",ans); } }
4. 1244F
大意: 给定一个环, 对于每个时刻, 考虑点$i$和它左右两侧的点, 若黑色个数多, 则点$i$变为黑色, 否则变为白色. 求$k$时间后每个点颜色.
首先可以观察到假设相邻两点同色, 那么这两点颜色永远不会改变. 所以就把这个环拆成若干个段, 每段之间是独立的, 分别模拟一下即可.
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 1e6+10; int n,k,vis[N],v[N]; char s[N],p[N]; void work(int l, int r, int k) { char A = s[(l-1+n)%n], B = s[(r+1)%n]; while (v[l]&&k) { s[l] = A, s[r] = B; v[l] = v[r] = 0; --k,(++l)%=n,r=(r-1+n)%n; } while (v[l]) v[l]=1,s[l]="BW"[s[(l-1+n)%n]=='B'],(++l)%=n; } int main() { scanf("%d%d%s",&n,&k,s); int pos = -1; REP(i,0,n-1) if (s[i]==s[(i-1+n)%n]) pos = i; if (pos<0) { if (k&1) REP(i,0,n-1) s[i]="BW"[s[i]=='B']; puts(s); return 0; } REP(i,0,n-1) { //标记每段的开头: BBWB if (s[(i-1+n)%n]==s[(i-2+n)%n]&&s[(i-1+n)%n]!=s[i]&&s[i]!=s[(i+1)%n]) vis[i] = 1; //标记每段的结尾: WBB if (s[(i+1)%n]==s[(i+2)%n]&&s[(i+1)%n]!=s[i]) vis[i] += 2; } REP(i,0,n-1) if (vis[i]&1) { int j = i; while (vis[j]<=1) v[j]=1,(++j)%=n; v[j]=1; work(i,j,k); } puts(s); }
5. 1288D
大意: 给定$n$个数组, 要求选出$i,j$, 构造数组$b_k=max(a_{i,k},a_{j,k})$, 满足$b_k$最小值最大.
状压求出状态为$s$的最小值的最大值$f[s]$, 那么答案就是$max(f[s],f[mx\oplus s])$
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 1e6+10; int n,m,a[N]; pii f[N]; void dfs(int d, int s, int x, int id) { if (d==m) return f[s]=max(f[s],pii(x,id)),void(); dfs(d+1,s,x,id),dfs(d+1,s^1<<d,min(x,a[d]),id); } int main() { scanf("%d%d",&n,&m); int mx = (1<<m)-1; REP(i,1,n) { REP(j,0,m-1) scanf("%d",a+j); dfs(0,0,2e9,i); } int ans = -1, id = 0; REP(i,0,mx) { int w = min(f[i].x,f[mx^i].x); if (w>ans) ans = w, id = i; } printf("%d %d\n",f[id].y,f[mx^id].y); }
6. 1285F
大意: 给定序列$a$, 求$\max\limits_{1\le i<j\le n}LCM(a_i,a_j)$
考虑枚举gcd, 问题就转化为求两个互质的数的乘积的最大值.
考虑从大到小遍历, 维护一个栈. 假设当前遍历到$x$, 每次从栈中取出最小元素$y$.
假设$x$和$y$互质的话, 那么$y$就可以从栈中取出, 这是因为以后遍历到的数会更小, 一定不会更优.直到栈中没有与$x$互质的为止.
求一个序列中是否有与$x$互质的数, 就相当于求
$\sum\limits_{i=1}^{mx}{cnt}_i[gcd(i,x)=1]=\sum\limits_{d|x}\mu(d)\sum\limits_{i=1}^{mx} [d|x]{cnt}_i$
动态维护$f(d) = \sum\limits_{i=1}^{mx} [d|x]{cnt}_i$即可.
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 1e5+10; int n,a[N],mu[N],f[N]; vector<int> c[N],s; int main() { REP(i,1,N-1) for (int j=i; j<N; j+=i) c[j].pb(i); mu[1] = 1; REP(i,1,N-1) for (int j=2*i; j<N; j+=i) mu[j]-=mu[i]; scanf("%d", &n); ll ans = 0; REP(i,1,n) { int t; scanf("%d", &t); a[t] = 1; ans = max(ans, (ll)t); } REP(g,1,N-1) { PER(x,1,(N-1)/g) if (a[g*x]) { int tot = 0; for (int t:c[x]) tot += mu[t]*f[t]; while (tot) { int y = s.back(); if (gcd(x,y)==1) { ans = max(ans, (ll)g*x*y); --tot; } for (int t:c[y]) --f[t]; s.pop_back(); } for (int t:c[x]) ++f[t]; s.push_back(x); } while (s.size()) { for (int t:c[s.back()]) --f[t]; s.pop_back(); } } printf("%lld\n", ans); }
7. 1313D
大意: 给定$n$个区间, 保证每个点最多被$k$个区间覆盖, 可以任意删除掉区间, 求最多多少个点被奇数个区间覆盖.
考虑离散化, 把连续一段相同状态的点合并一下, 然后状压$DP$即可.
#include <iostream> #include <cstring> #include <algorithm> #include <cstdio> #include <queue> #include <set> #include <map> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define x first #define y second #define pb push_back using namespace std; typedef pair<int,int> pii; int n,m,k; vector<pii> events; set<int> s; int dp[2][260]; int main() { scanf("%d%d%d",&n,&m,&k); REP(i,1,n) { int l,r; scanf("%d%d",&l,&r); events.pb(pii(l,i)); events.pb(pii(r+1,-i)); } sort(events.begin(),events.end()); s.insert(events[0].y); map<int,int> ID[2]; int cur = 0; auto upd = [&]() { cur ^= 1; memset(dp[cur],0xef,sizeof dp[0]); ID[cur].clear(); int p = 0, mx = (1<<s.size())-1; for (auto &t:s) ID[cur][t] = p++; }; auto get = [&](int x) { int sta = 0, p = 0; for (auto &t:ID[cur]) if (x>>(p++)&1) { if (ID[cur^1].count(t.x)) sta ^= 1<<ID[cur^1][t.x]; } return sta; }; for (int i=1; i<events.size(); ++i) { int len = events[i].x-events[i-1].x; if (len&&s.size()) { upd(); int mx = (1<<s.size())-1; if (ID[cur^1].empty()) { int ma = *max_element(dp[cur^1],dp[cur^1]+256); REP(j,0,mx) dp[cur][j] = ma+__builtin_parity(j)*len; } else { REP(j,0,mx) dp[cur][j] = max(dp[cur][j], dp[cur^1][get(j)]+__builtin_parity(j)*len); } } int id = events[i].y; if (id<0) { id = -id, s.erase(id), upd(); int mx = (1<<s.size())-1; REP(j,0,mx) { int sta = get(j); dp[cur][j] = max(dp[cur^1][sta],dp[cur^1][sta^(1<<ID[cur^1][id])]); } } else s.insert(id); } int ans = 0; REP(i,0,255) ans = max(ans, dp[cur][i]); printf("%d\n", ans); }
8. 1325F
大意: 给定$n$节点无向连通图, 求找到一个$\lceil \sqrt{n} \rceil$个点的独立集, 或长度不少于$\lceil \sqrt{n} \rceil$的简单环.
考虑$dfs$树, 对深度按$mod \lceil \sqrt{n} \rceil-1$分类, 那么如果找不到环的话, 每个分类的点之间一定不含边, 可以构成独立集, 并且最大的分类点数一定是不少于$\lceil \sqrt{n} \rceil$
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 1e6+50; int n,m,k,dep[N],fa[N],vis[N]; vector<int> g[N],h[N]; void get(int x, int y) { vector<int> v; do v.pb(x),x=fa[x]; while (x!=y); v.pb(y); printf("2\n%d\n",(int)v.size()); for (int t:v) printf("%d ",t); hr,exit(0); } void dfs(int x, int f, int d) { vis[x]=1,dep[x]=d,fa[x]=f; h[d%(k-1)].pb(x); for (int y:g[x]) if (y!=f) { if (!vis[y]) dfs(y,x,d+1); else if (d-dep[y]>=k-1) get(x,y); } } int main() { scanf("%d%d",&n,&m); k = ceil(sqrt(n)); REP(i,1,m) { int u,v; scanf("%d%d",&u,&v); g[u].pb(v),g[v].pb(u); } dfs(1,0,0); REP(i,0,k-2) if (h[i].size()>=k) { puts("1"); REP(j,0,k-1) printf("%d ", h[i][j]); return hr,0; } }
9. 1325E
大意: 给定序列, 每个元素最多$7$个因子, 求一个最短的积为完全平方数的子序列.
显然每个数最多只有两个素因子, 考虑对每个素数建一个点, 如果一个元素有两个素因子, 那么就再它们之间连一条边, 否则再建一个虚点, 把这个素数向虚点连边. 那么显然这个图的最小环就是答案.
可以注意到对于$> \sqrt{max A_i}$的点之间是不含边的, 所以一个环一定至少包含一个$<\sqrt{maxA_i}$的点, 所以可以枚举这个点, 用$bfs$求最小环, 复杂度就为$O(168n)$
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 1e6+50; int n,a[N],cnt,v[N],primes[N],d1[N],d2[N],f1[N],f2[N]; vector<int> g[N],d[N]; struct _ {int x,d,f;}; queue<_> q; int main() { REP(i,2,1000) if (!v[i]) { primes[++cnt] = i; for (int j=i*i; j<=1000; j+=i) v[j]=1; } scanf("%d", &n); REP(i,1,n) scanf("%d",a+i); REP(i,1,n) { REP(j,1,cnt) if (a[i]%primes[j]==0) { int x = 0; while (a[i]%primes[j]==0) ++x,a[i]/=primes[j]; if (x&1) d[i].pb(j); } if (a[i]>1) d[i].pb(a[i]); if (d[i].empty()) return puts("1"),0; if (d[i].size()==1) g[0].pb(d[i][0]),g[d[i][0]].pb(0); else g[d[i][0]].pb(d[i][1]),g[d[i][1]].pb(d[i][0]); } int ans = INF; REP(i,0,cnt) { sort(g[i].begin(),g[i].end()); if (unique(g[i].begin(),g[i].end())!=g[i].end()) return puts("2"); d1[0] = d2[0] = INF, f1[0] = f2[0] = -1; REP(j,1,n) for (int t:d[j]) d1[t]=d2[t]=INF,f1[t]=f2[t]=-1; for (int t:g[i]) q.push({t,1,t}); while (q.size()) { auto [u,d,f] = q.front(); q.pop(); if (d1[u]==INF) d1[u]=d,f1[u]=f; else if (d2[u]==INF&&f1[u]!=f) d2[u]=d,f2[u]=f; else continue; for (int v:g[u]) if (v!=i) q.push({v,d+1,f}); } for (int v:g[i]) ans = min(ans, d2[v]+1); } if (ans>n) ans = -1; printf("%d\n", ans); }
10. 1326E
大意: 给定排列$p,q$, $p_i$表示每个元素的值, $q_i$表示位置$q_i$有炸弹. 维护一个集合$A$, 从左到右依次把$p_i$添加到$A$中, 如果$i$位置有炸弹就将$A$中最大元素弹出. 对于$1\le i\le n$, 输出只考虑$q_1,q_2,...,q_{i-1}$位置有炸弹时最后$A$中的最大元素.
跟756C非常像. 假设答案为$x$, 那么只用考虑$p$中$\ge x$的元素, 每次添加一个$\ge x$的元素就等价于一次$push$操作, 每碰到一个炸弹就等价于一次$pop$操作, 假设最终栈中仍有元素, 那么就说明答案是$\ge x$的. 所以问题就转化为给定一个$push$和$pop$的操作序列, 求判断最终栈是否非空. 把$push$看做$1$, $pop$看做$-1$, 栈非空就等价于存在一个后缀和$>0$, 用线段树维护一个最大后缀和即可.
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 1e6+10; int n,a[N],b[N],pos[N]; struct _ { int ma,sum; } tr[N<<2]; void pu(int o) { tr[o].sum=tr[lc].sum+tr[rc].sum; tr[o].ma=max(tr[rc].ma,tr[rc].sum+tr[lc].ma); } void add(int o, int l, int r, int x, int v) { if (l==r) { tr[o].sum += v; tr[o].ma = max(0,tr[o].sum); return; } mid>=x?add(ls,x,v):add(rs,x,v); pu(o); } int main() { scanf("%d",&n); REP(i,1,n) scanf("%d",a+i),pos[a[i]]=i; REP(i,1,n) scanf("%d",b+i); add(1,1,n,pos[n],1); int ans = n; printf("%d ",ans); REP(i,1,n-1) { add(1,1,n,b[i],-1); while (!tr[1].ma) add(1,1,n,pos[--ans],1); printf("%d ", ans); } puts(""); }
11. 1326F1
大意: $n$个人, 给定$n\times n$矩阵表示每个人的认识关系. 一个排列$p$对应一个长$n-1$的二进制数, 假设$p_i$与$p_{i+1}$认识, 那么第$i$位为$1$, 否则为$0$. 对于所有长$n-1$的二进制数, 输出它对应多少种排列.
设$f_{i,j,k}$表示只考虑集合$i$, 排列长度为$j$, 排列中最后一个数为$k$, 得到的二进制数为$x$的方案数.
考虑状态转移, 每次枚举一个不在$i$中的数$t$添加到排列末尾.
如果$t$和$k$不认识, 那么$f_{i\oplus 2^{x},j+1,t,x} \leftarrow f_{i,j,k,x}$
如果$t$和$k$认识, 那么$f_{i\oplus 2^{x},j+1,t,x\oplus 2^{j}} \leftarrow f_{i,j,k,x}$
这样的话复杂度是$O(n^3 4^n)$.
实际上可以注意到$j$就是$i$中$1$的个数, 这样就可以优化掉一个$n$, 并且有效的状态$x$的个数是$2^j$, 如果只遍历有效状态的话, 复杂度就可以达到$O(n^2 3^n)$
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head int n; vector<ll> f[1<<14][14]; char s[14][14]; ll ans[1<<14]; int main() { scanf("%d", &n); REP(i,0,n-1) scanf("%s", s[i]); int mx = (1<<n)-1; REP(i,0,mx) REP(j,0,n-1) if (i>>j&1) f[i][j].resize(1<<__builtin_popcount(i)-1); REP(i,0,n-1) f[1<<i][i][0] = 1; REP(i,1,mx) REP(j,0,n-1) if (i>>j&1) { int m = f[i][j].size()-1; REP(k,0,m) { REP(x,0,n-1) if (i>>x&1^1) { int nxt = k; if (s[j][x]=='1') nxt ^= m+1; f[i^(1<<x)][x][nxt] += f[i][j][k]; } if (i==mx) ans[k] += f[i][j][k]; } } mx = (1<<n-1)-1; REP(i,0,mx) printf("%lld ",ans[i]);hr; }
12. 1326F2
大意: 1326F1的加强版
$n$个人可以看成是一个无向图, 那么一个二进制数就对应图中的若干条链.
例如$111000111011$对应链的长度集合为$\{ 4,1,1,4,3\}$.
那么可以发现只要对应的链的长度集合相同, 答案就相同.
这个集合就相当于是$n$的整数拆分, $n=18$时, 拆分方案有$385$种.
那么只要暴搜出所有拆分, 求出每个拆分的答案即可.
对于一个拆分, 必须要保证相邻两条链之间是没有边的, 这个比较难处理.
比方说要求$\{1,1,2\}$的答案, 会把$\{1,3\},\{2,2\},\{4\}$的答案也算上.
可以注意到$\{1,1,2\},\{1,3\},\{2,2\},\{4\}$对应的二进制数是$001,011,101,111$.
也就是说$\{1,1,2\}$刚好是其他多算的二进制数的子集.
所以先求出不考虑相邻链是否有边的答案, 最后再做一次$IFWT_{and}$即可.
先用$dp$求出$f_{len,S}$表示长$len$,状态为$S$的链的条数.
那么可以得到拆分$T$的答案就为$\sum\prod f_{T_i,m_i}$
$m_i$是第$i$条链的状态, 并且$m_1,...,m_{|T|}$的二进制或和等于$2^{n}-1$. (因为$T_i$的和一定为$n$,就不用考虑$m_i$交叉的情况)
这个式子显然是$f_{T_i}$的集合并卷积的第$2^{n}-1$项, 可以用$FWT_{or}$求出.
总复杂度为$O((385 n+n^2)2^n)$
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 18; int n, cnt; ll dp[1<<N][N],f[N+1][1<<N],h[1<<N],d[400],ans[1<<N]; char a[N][N]; map<vector<int>,int> ID; vector<int> g[400],v; void dfs(int d, int s) { if (s>n) return; if (s==n) { ID[v] = ++cnt; g[cnt] = v; return; } REP(i,d,n) v.pb(i),dfs(i,s+i),v.pop_back(); } void FWT_or(ll *a, int n, int tp) { int mx = (1<<n)-1; REP(i,0,n-1) REP(j,0,mx) { if (j>>i&1) a[j]+=tp*a[j^1<<i]; } } void FWT_and(ll *a, int n, int tp) { int mx = (1<<n)-1; REP(i,0,n-1) REP(j,0,mx) { if (j>>i&1) a[j^1<<i]+=tp*a[j]; } } int main() { scanf("%d", &n); REP(i,0,n-1) scanf("%s",a[i]); dfs(1,0); REP(i,0,n-1) dp[1<<i][i] = 1; int mx = (1<<n)-1; REP(i,0,mx) { REP(j,0,n-1) if (i>>j&1) REP(k,0,n-1) if (i>>k&1^1) { if (a[j][k]=='1') dp[i^1<<k][k] += dp[i][j]; } int len = __builtin_popcount(i); REP(j,0,n-1) f[len][i] += dp[i][j]; } REP(i,1,n) FWT_or(f[i],n,1); REP(i,1,cnt) { REP(j,0,mx) h[j] = 1; for (int j:g[i]) REP(k,0,mx) h[k]*=f[j][k]; FWT_or(h,n,-1); d[i] = h[mx]; } mx = (1<<n-1)-1; REP(i,0,mx) { v.clear(); REP(j,0,n-1) { int k = j; while (k<n-1&&(i>>k&1)) ++k; v.pb(k-j+1); j = k; } sort(v.begin(),v.end()); ans[i] = d[ID[v]]; } FWT_and(ans,n-1,-1); REP(i,0,mx) printf("%lld ", ans[i]);hr; }
13. 1327F
大意: 长$n$的序列, 每个元素范围$[0,2^{k}-1]$, 给定$m$个限制$(l_i,r_i,x_i)$, 表示$a_l,...a_r$的二进制与和等于$x$, 求有多少种方案.
单独考虑二进制每位的答案, 假设$x_i$当前位为$0$, 那么就要求$[l_i,r_i]$中至少有$1$个$0$, 假设$x_i$当前位为$1$, 那么就要求$[l_i,r_i]$必须全为$1$. 那么枚举$1$和$0$的上次出现位置, 很容易写出$O(kn^2)$的$dp$.
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 998244353, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 1e5+10; int n,k,m,l[N],r[N],x[N]; vector<pii> f[N]; int dp[2][2][N]; void add(int &a, int b) {a+=b;if (a>=P)a-=P;} int solve(int d) { REP(i,1,n) f[i].clear(); REP(i,1,m) f[r[i]].pb(pii(l[i],x[i]>>d&1)); int cur = 0; memset(dp[cur],0,sizeof dp[0]); dp[0][0][0] = 1; //dp[i][z][j] //z = 0, 表示i填0, 上一个1的位置为j的方案数 //z = 1, 表示i填1, 上一个0的位置为j的方案数 REP(i,1,n) { cur ^= 1; memset(dp[cur],0,sizeof dp[0]); REP(j,0,i-1) REP(z,0,1) { int &r = dp[!cur][z][j]; add(dp[cur][z][j],r); add(dp[cur][!z][i-1],r); } for (auto &e:f[i]) { if (e.y==0) { REP(j,0,e.x-1) dp[cur][1][j] = 0; } else { REP(j,0,i-1) dp[cur][0][j] = 0; REP(j,e.x,i-1) dp[cur][1][j] = 0; } } } int ans = 0; REP(i,0,n-1) REP(z,0,1) add(ans,dp[cur][z][i]); return ans; } int main() { scanf("%d%d%d",&n,&k,&m); REP(i,1,m) scanf("%d%d%d",l+i,r+i,x+i); int ans = 1; REP(i,0,k-1) ans = (ll)ans*solve(i)%P; printf("%d\n", ans); }
然后考虑$O(kn)$的做法, 假设$f_i$表示第$i$个数取$0$的方案数, 如果从$f_j$转移到$f_i$的话, 也就是说$[j+1,i-1]$全填$1$, 那么合法的$j$必须大于等于所有满足$r\le i-1$的$0$区间的$l$, 所以维护$0$区间的左端点最大值$ma$, 那么可转移的$j$的范围就是$[ma,i-1]$, 前缀优化一下即可. 对于$1$区间, 只要限制它覆盖的每个点$f_i$都为$0$即可. 这样复杂度就为$O(kn)$
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <cmath> #include <set> #include <map> #include <queue> #include <string> #include <cstring> #include <bitset> #include <functional> #include <random> #define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i) #define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(_a) ({REP(_i,1,n) cout<<_a[_i]<<',';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 998244353, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 1e6+10; int n,k,m,l[N],r[N],x[N],c[N],ma[N],f[N]; void add(int &a, int b) {a=(a+b)%P;} int solve(int d) { REP(i,0,n+1) c[i] = ma[i] = f[i] = 0; REP(i,1,m) { if (x[i]>>d&1) ++c[l[i]], --c[r[i]+1]; else ma[r[i]] = max(ma[r[i]], l[i]); } REP(i,1,n) ma[i] = max(ma[i], ma[i-1]); f[0] = 1; int sum = 0; REP(i,1,n+1) { sum += c[i]; if (!sum) f[i] = (f[i-1]-(ma[i-1]?f[ma[i-1]-1]:0))%P; add(f[i],f[i-1]); } return f[n+1]-f[n]; } int main() { scanf("%d%d%d",&n,&k,&m); REP(i,1,m) scanf("%d%d%d",l+i,r+i,x+i); int ans = 1; REP(i,0,k-1) ans = (ll)ans*solve(i)%P; if (ans<0) ans += P; printf("%d\n", ans); }
14. 1316F