杂题乱做
MY
- Bubble Cup X Finals Neural Network country
- Codeforces Round #429 Leha and another game about graph
- 洛谷 P3599 Koishi Loves Construction
- ARC102B All Your Paths are Different Lengths
- AGC025D Choosing Points
- AGC027F Grafting
T1
给定一个除源点和汇点外有 \(L\) 层,每层 \(N\) 个点的有向图,相邻层数之间都有连边,相邻层数之间边的权值是这条边指向的点的权值(每层编号相同的点权值相同),告诉你源点到第一层边的权值,每个点的权值,最后一层到汇点的权值,求源点到汇点的路径长度能被 \(m\) 整除的个数。
先考虑 brute force : 不难发现相邻层之间点的编号影响不了什么,设 \(dp_{i,j}\) 表示在第 \(i\) 层,路径长度为 \(j\) 的方案数。
不难得出转移:\(dp_{i,j}=\displaystyle\sum_{k=0}^{m} dp_{i-1,j-k} \times cnt_k\),这里 \(cnt_k\) 表示 \(k\) 在相邻两层之间的出现次数。
可是在最后一层时,边的权值与点的编号有关,这个方程就不对了。
我们把最后一层和汇点的边合并,然后分开来处理。
具体来说就是这样:
My Code
for(int i=1; i<=n; i++) {
ed[(read()%m+val[i])%m]++;
}
/* dp */
for(int i=0; i<m; i++) {
for(int k=0; k<m; k++) {
f[l][(i+k)%m]+=f[l-1][i]*ed[k];
}
}
发现 \(m\) so small, \(L\) so big 不难想到矩阵乘法。
我们观察一下这个 DP:
My Code
f[0][0]=1;
for(int i=2; i<l; i++) {
for(int j=0; j<m; j++) {
for(int k=0; k<m; k++) {
f[i][(j+k)%m]+=f[i-1][j]*w[k];
}
}
}
它和矩阵乘法有着相同的性质(结合律之类的)。
这种类矩阵乘法可以这么写:
My Code
class matrix {
public:
int c[M];
matrix(){memset(c,0,sizeof(c));}
matrix operator *(const matrix &bb) const {
matrix ret;
for(int i=0; i<m; i++) {
for(int j=0; j<m; j++) {
ret.c[(i+j)%m]=(ret.c[(i+j)%m]+c[i]*bb.c[j]%Mod)%Mod;
}
}
return ret;
}
} fr,f,ed;
然后做快速幂就可以了。
T2
考虑先建 DFS 树。
对于树上的一个节点 \(u\),我们判断它子树内的选择情况是否能满足它的度数限制,如果可以就不管,如果不可以就将 \(fa_u\) 和 \(u\) 的边选上。
这样最后不满足限制的就只可能有 \(rt\),解决方法就是选择一个限制为 -1
的节点 \(v\),将 \(v \to rt\) 上的节点全部取反。
如果没有就无解。
My Code
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+8;
inline int read() {
int s=1,a=0;char c=getchar();
while(!isdigit(c)) {if(c=='-') s=-s;c=getchar();}
while(isdigit(c)) {a=(a<<3)+(a<<1)+(c^48),c=getchar();}
return s*a;
}
int n,m;
struct graph {
int fr,to,d,id;
} g[N];
struct edge {
int nxt,to,id;
} e[N<<1];
class UFS {
private:
int fa[N];
public:
void init() {
for(int i=1; i<=n; i++) fa[i]=i;
}
int find(int x) {
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y) {
int fx=find(x),fy=find(y);
if(fx==fy) return;
fa[fy]=fx;
}
} dsu;
int head[N],idx,fa[N],ch[N],w[N],deg[N],nd;
void add(int u,int v,int id) {
e[++idx].nxt=head[u];
e[idx].to=v;
e[idx].id=id;
head[u]=idx;
}
void build() {
dsu.init();
int cnt=0;
for(int i=1; i<=m; i++) {
int u=g[i].fr,v=g[i].to;
int fx=dsu.find(u),fy=dsu.find(v);
if(fx!=fy) {
cnt++;
dsu.merge(fx,fy);
add(u,v,g[i].id);
add(v,u,g[i].id);
}
if(cnt==n-1) break;
}
}
int flg=0;
void dfs1(int u,int f) {
if(deg[u]==-1) flg=1,nd=u;
for(int i=head[u]; i; i=e[i].nxt) {
int v=e[i].to;
if(v==f) continue;
fa[v]=u;
w[v]=e[i].id;
dfs1(v,u);
}
}
void dfs2(int u,int f) {
int cnt=0;
for(int i=head[u]; i; i=e[i].nxt) {
int v=e[i].to;
if(v==f) continue;
dfs2(v,u);
if(ch[e[i].id]) cnt++;
}
if(deg[u]==-1||(cnt&1)==deg[u]) return;
else ch[w[u]]=1;
}
bool check() {
int cnt=0;
for(int i=head[1]; i; i=e[i].nxt) {
int v=e[i].to;
if(ch[e[i].id]) cnt++;
}
// cout<<cnt<<" "<<deg[1]<<endl;
if(deg[1]==-1||(cnt&1)==deg[1]) return true;
return false;
}
void rework() {
int u=nd;
while(u!=1) {
ch[w[u]]^=1;
u=fa[u];
}
}
vector <int> ans;
void getans(int u) {
for(int i=head[u]; i; i=e[i].nxt) {
int v=e[i].to;
if(v==fa[u]) continue;
if(ch[e[i].id]) ans.push_back(e[i].id);
getans(v);
}
}
int main() {
n=read(),m=read();
for(int i=1; i<=n; i++) {
deg[i]=read();
}
for(int i=1; i<=m; i++) {
int u=read(),v=read();
g[i].fr=u,g[i].to=v;
g[i].id=i;
}
build();
dfs1(1,0);
dfs2(1,0);
if(!check()) {
// cout<<1<<endl;
if(!flg) puts("-1"),exit(0);
else rework();
}
getans(1);
sort(ans.begin(),ans.end());
printf("%d\n",ans.size());
for(auto v:ans) {
printf("%d\n",v);
}
return 0;
}
T3
- 对于 Task1
我们肯定可以发现 \(n\) 一定要放在最前面。
否则假设 \(n\) 在位置 \(i\),则 \(sum_{i-1} \equiv sum_{i} \pmod{n}\)。
然后就可以得出长度一定为偶数(\(1\) 除外),如果为 \(1\) 以外的奇数,则:\(\displaystyle\sum_{i=2}^{n}a_i=\displaystyle\sum_{i=1}^{n-1}=n\times \frac{n-1}{2} \equiv 0 \pmod{n}\)。
如果是偶数 \(n\) 会被 \(2\) 约掉。
否则,我们考虑构造一个这样的序列 \(0,-1,2,-3,\cdots ,-(n-1)\)。
在模意义下,这就是 \(n\) 的排列。
- 对于 Task2
考虑当 \(n\) 为合数时,那么一定有两个数 \(x,y\) 使得 \(xy \equiv 0 \pmod{n}\)。
当然 \(4\) 是个特例特例,就是敌人。
当 \(n\) 为质数是,一定可以构造一个这样的序列 \(1,\displaystyle\frac{2}{1},\frac{3}{2},\cdots,\frac{n-1}{n-2},\frac{n}{n-1}\)。
在模意义下,这就是 \(n\) 的排列。
My Code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+8;
inline int read() {
int s=1,a=0;char c=getchar();
while(!isdigit(c)) s=(c=='-')?(-s):s,c=getchar();
while(isdigit(c)) a=(a<<3)+(a<<1)+(c^48),c=getchar();
return s*a;
}
class subtask1 {
private:
int a[N],n;
public:
void solve() {
n=read();
if((n&1)&&(n^1)) puts("0");
else {
printf("2 ");
if(n==1) printf("1");
else {
for(int i=1; i<=n; i++) {
printf("%d ",(i&1)?(n-i+1):(i-1));
}
}
puts("");
}
}
} sol1;
class subtask2 {
#define int long long
private:
int a[N],n;
int notprime[N],prime[N],cnt;
int ksm(int a,int b) {
int c=1;
while(b) {
if(b&1) c*=a,c%=n;
a*=a,a%=n;
b>>=1;
}
return c;
}
public:
void init() {
notprime[0]=notprime[1]=1;
for(int i=2; i<N; i++) {
if(!notprime[i]) {
prime[++cnt]=i;
}
for(int j=1; j<=cnt&&i*prime[j]<N; j++) {
if(i%prime[j]==0) {
notprime[i*prime[j]]=1;
break;
}
notprime[i*prime[j]]=1;
// cout<<i*prime[j]<<" "<<notprime[i*prime[j]]<<endl;
}
}
}
void solve() {
n=read();
if(notprime[n]&&n!=1&&n!=4) puts("0");
else {
printf("2 ");
if(n==1) printf("1");
else if(n==4) printf("1 3 2 4");
else {
printf("1 ");
for(int i=2; i<n; i++) {
printf("%d ",ksm(i-1,n-2)*i%n);
}
printf("%d",n);
}
puts("");
}
}
#undef int
} sol2;
int main() {
int X=read(),T=read();
if(X==2) sol2.init();
while(T--) {
if(X==1) sol1.solve();
else sol2.solve();
}
return 0;
}
T4
考虑当 \(L=2^k\) 时,我们可以用如下方式构造:建 \(k+1\) 个点,\(i \to i+1 \ with \ 0\) 和 \(1 \to i+1 \ with \ 2^i\)。
考虑更一般的情况,先找到 \(2^k \le L\) 最大的 \(k\),对于 \(L\) 的每一位 \(i\),如果这一位为 \(1\),我们就向 \(k+1\) 连一条边权为 \(L\) 抹去 \(i\) 及其以下的位的边。
这样 \([0,L-1]\) 之间的每一种边权都出现了一次。
点数最多为 \(\lfloor \log L \rfloor +1\),边数最多为 \(3\lfloor \log L \rfloor\) 条,满足条件。
My Code
#include<bits/stdc++.h>
using namespace std;
int L,w,res;
inline int read() {
int s=1,a=0;char c=getchar();
while(!isdigit(c)) s=(c=='-')?(-s):s,c=getchar();
while(isdigit(c)) a=(a<<3)+(a<<1)+(c^48),c=getchar();
return s*a;
}
struct edge {
int fr,to,dis;
};
vector <edge> ans;
int main() {
L=read();
int f=L;
while(f) {
w++;
f>>=1;
}
for(int i=1; i<w; i++) {
ans.push_back((edge){i,i+1,0});
ans.push_back((edge){i,i+1,(1<<(i-1))});
}
for(int i=0; i<w-1; i++) {
if((L>>i)&1) {
ans.push_back((edge){i+1,w,L&(~((1<<(i+1))-1))});
}
}
printf("%d %d\n",w,ans.size());
for(auto v:ans) {
printf("%d %d %d\n",v.fr,v.to,v.dis);
}
return 0;
}
T5
考虑只有一个 \(D\) 时怎么做。
我们将距离恰好为 \(D\) 的点连边,可以证明它一定是二分图。
证明:
设有两个点 \((x_1,y_1)\) 和 \((x_2,y_2)\) 的距离为 \(D\)。
则 \((x_1-x_2)^2+(y_1-y_2)^2=D\)。
我们可以发现 \((x_1-x_2)^2\) 的奇偶性和 \((x_1-x_2)\) 的奇偶性是一样的。
所以 \((x_1-x_2)^2+(y_1-y_2)^2\) 的奇偶性和 \(x_1+y_1-(x_2+y_2)\) 的奇偶性是一样的。
-
当 \(D\) 为奇数时,\(x_1+y_1\) 和 \(x_2+y_2\) 的奇偶性显然不一样,所以一定是二分图。
-
当 \(D\) 为偶数时,奇偶性是一样的,就不好判断。
但我们还是可以判断出来。
以下默认 \(D\) 为偶数。
-
当 \(D\equiv 2 \pmod{4}\) 时,\(x_1-x_2\) 和 \(y_1-y_2\) 一定是奇数,即 \(x_1,x_2\) 和 \(y_1,y_2\) 的奇偶性一定不同,所以一定是形如(奇,奇)连(偶,偶),也是二分图。
-
当 \(4|D\) 时,我们可以把原式转成 \(4(x_1^\prime-x_2^\prime)^2+4(y_1^\prime-y_2^\prime)^2 = D\) 的形式,然后两边约 \(4\),最终一定可以转成上一种情况。
证毕。
对其黑白染色,发现同种颜色一定可以一起选。
对于有两个 \(D\),建两个图,我们将原点集划分成四个不相交的集合:
- 在第一幅图中为黑,在第二幅图中为黑。
- 在第一幅图中为白,在第二幅图中为黑。
- 在第一幅图中为黑,在第二幅图中为白。
- 在第一幅图中为白,在第二幅图中为白。
根据鸽巢原理,一定有一个大于 \(O(n^2)\)。
My Code
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int s=1,a=0;char c=getchar();
while(!isdigit(c)) {if(c=='-') s=-s;c=getchar();}
while(isdigit(c)) {a=(a<<3)+(a<<1)+(c^48),c=getchar();}
return s*a;
}
const int N=700;
int n,d1,d2;
int getpos(int x,int y) {
return (x-1)*2*n+y;
}
inline bool in(int x,int y) {
return (x>=1)&&(x<=n*2)&&(y>=1)&&(y<=n*2);
}
class Graph {
private:
vector <int> G[N*N];
int vis[N*N],col[N*N];
public:
int dd;
Graph(){memset(col,0,sizeof(col)),memset(vis,0,sizeof(vis));}
void add(int u,int v) {
G[u].push_back(v);
G[v].push_back(u);
}
void make_edge() {
for(int i=1; i<=2*n; i++) {
for(int j=1; j<=2*n; j++) {
for(int x=0; x*x<=dd; x++) {
int y=sqrt(dd-x*x);
if(x*x+y*y==dd) {
if(in(i+x,j+y)) add(getpos(i,j),getpos(i+x,j+y));
if(in(i-x,j+y)) add(getpos(i,j),getpos(i-x,j+y));
if(in(i+x,j-y)) add(getpos(i,j),getpos(i+x,j-y));
if(in(i-x,j-y)) add(getpos(i,j),getpos(i-x,j-y));
}
}
}
}
}
void dfs(int u,int w) {
vis[u]=1;
col[u]=w;
for(auto v:G[u]) {
if(!vis[v]) {
dfs(v,w^1);
}
}
}
void solve() {
for(int i=1; i<=4*n*n; i++) {
if(!vis[i]) dfs(i,0);
}
}
int c(int v) {
return col[v];
}
} g1,g2;
vector <pair<int,int>> S[4];
vector <pair<int,int>> work(int c1,int c2) {
vector <pair<int,int>> ret;
ret.clear();
for(int i=1; i<=2*n; i++) {
for(int j=1; j<=2*n; j++) {
if(g1.c(getpos(i,j))==c1&&g2.c(getpos(i,j))==c2) {
ret.push_back(make_pair(i,j));
// cout<<i<<" "<<j<<" "<<c1<<" "<<c2<<endl;
}
}
}
return ret;
}
int main() {
n=read(),g1.dd=read(),g2.dd=read();
g1.make_edge();
g1.solve();
g2.make_edge();
g2.solve();
S[0]=work(0,0);
S[1]=work(0,1);
S[2]=work(1,0);
S[3]=work(1,1);
vector <pair<int,int>> res;
res.clear();
for(int i=0; i<4; i++) {
if(res.size()<S[i].size()) res=S[i];
}
int cnt=0;
for(auto v:res) {
cnt++;
if(cnt<=n*n)
printf("%d %d\n",v.first-1,v.second-1);
}
return 0;
}
T6
贺题有没有贺懂,尝试着讲一下就好了。
我还是太菜了。
考虑枚举第一次操作,枚举一个点 \(u\) 将其接到 \(v\) 上。
此时 \(u\) 肯定不会动了,我们将 \(u\) 作为根(因为根肯定不会动)。
考虑一个点 \(v\),它什么时候要动,它要动当它的 \(fa_{A}\not= fa_{B}\) 时。
因为树根不能动,所以操作都是从叶子开始。
也就是说:如果不去操作一个点 \(v\) 的话,那么 \(v\) 的双亲节点将永远不会改变。
但是如果操作了一个点的话,又把它接回原来的位置显然不优,所以可以得出结论:
- 一个点被操作,当且仅当它在两棵树中的双亲节点不同。
当一个点的 \(fa_{A}\) 要动时,它肯定要先动,否则 \(fa_{A}\) 无法成为叶子。
当一个点的 \(fa_{B}\) 要动时,它肯定要后动,否则 \(fa_{B}\) 也动不了。
根据以上约束连边,拓扑排序即可。
My Code
#include<bits/stdc++.h>
using namespace std;
const int N=100;
inline int read() {
int s=1,a=0;char c=getchar();
while(!isdigit(c)) s=(c=='-')?-s:s,c=getchar();
while(isdigit(c)) a=(a<<3)+(a<<1)+c-'0',c=getchar();
return s*a;
}
vector <int> G[3][N];
int n,fa[3][N],deg[3][N];
void dfs(int id,int u,int fat) {
fa[id][u]=fat;
for(auto v:G[id][u]) {
if(v==fa[id][u]) continue;
dfs(id,v,u);
}
}
int ans;
int ch[N];
int topsort(int id) {
queue <int> q;
int cnt=0;
for(int i=1; i<=n; i++) {
if(!deg[id][i]&&ch[i]) q.push(i);
}
while(q.size()) {
int u=q.front();
q.pop();
cnt++;
for(auto v:G[id][u]) {
--deg[id][v];
if(!deg[id][v]) {
q.push(v);
}
}
}
return cnt;
}
int solve() {
int num=0;
memset(ch,0,sizeof(ch));
for(int i=1; i<=n; i++) {
if(fa[0][i]!=fa[1][i]) {
ch[i]=1;
num++;
}
}
// cout<<1<<endl;
if(num>ans) return n+1;
for(int i=1; i<=n; i++) G[2][i].clear();
memset(deg[2],0,sizeof(deg[2]));
// cout<<1<<endl;
for(int i=1; i<=n; i++) {
if(!ch[i]) {
if(ch[fa[0][i]]) return n+1;
continue;
}
if(ch[fa[0][i]]) G[2][i].push_back(fa[0][i]),deg[2][fa[0][i]]++;
if(ch[fa[1][i]]) G[2][fa[1][i]].push_back(i),deg[2][i]++;
}
// cout<<1<<endl;
return topsort(2)==num?num:n+1;
}
int main() {
int T=read();
while(T--) {
n=read();
memset(deg,0,sizeof(deg));
for(int i=1; i<=n; i++) G[0][i].clear(),G[1][i].clear();
for(int i=1; i<n; i++) {
int u=read(),v=read();
G[0][u].push_back(v);
G[0][v].push_back(u);
deg[0][u]++,deg[0][v]++;
}
for(int i=1; i<n; i++) {
int u=read(),v=read();
G[1][u].push_back(v);
G[1][v].push_back(u);
deg[1][u]++,deg[1][v]++;
}
ans=n+1;
for(int u=1; u<=n; u++) {
fa[0][u]=fa[1][u]=0;
// cout<<1<<endl;
dfs(0,u,0),dfs(1,u,0);
// cout<<2<<endl;
ans=min(ans,solve());
// cout<<3<<endl;
if(deg[0][u]==1) {
for(int x=1; x<=n; x++) {
if(x!=u) {
fa[0][x]=u;
dfs(0,x,u);
dfs(1,u,0);
fa[0][u]=fa[1][u]=0;
ans=min(ans,solve()+1);
}
}
}
}
printf("%d\n",ans>n?-1:ans);
}
return 0;
}
LJY's
- Educational Codeforces Round 19 Array Queries
- POI2013 Triumphal arch
- Codeforces Beta Round #47 Bombing
- Codeforces Round #345 Table Compression
- Educational Codeforces Round 36 Coprime Arrays
T1
考虑根号分治。
-
当 \(k\le \sqrt{n}\) 时,显然可以预处理。具体实现就是用一个数组 \(st_{p,k}\) 表示答案,然后如果 \(a_p+p+k>n\) 时就是 \(1\),否则从 \(st_{a_p+p+k,k}\) 转移过来。
-
当 \(k> \sqrt{n}\) 时,步数绝对不会很多,直接暴力即可。
时间复杂度 \(O(n\displaystyle\sqrt{n})\)。
My Code
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int s=1,a=0;
char c=getchar();
while(!isdigit(c)) {
if(c=='-') s=-s;
c=getchar();
}
while(isdigit(c)) {
a=a*10+c-'0';
c=getchar();
}
return s*a;
}
const int N=1e5+8;
int st[N][400],a[N];
int n,q,lim;
void init() {
for(int k=1; k<=lim; k++) {
for(int i=n; i>=1; i--) {
if(a[i]+i+k>n) st[i][k]=1;
else st[i][k]=st[a[i]+i+k][k]+1;
}
}
}
int main() {
n=read();
for(int i=1; i<=n; i++) a[i]=read();
lim=sqrt(n);
init();
q=read();
while(q--) {
int p=read(),k=read();
if(k<=lim) {
printf("%d\n",st[p][k]);
}
else {
int ret=0,fp=p;
while(fp<=n) {
fp=fp+a[fp]+k;
ret++;
}
printf("%d\n",ret);
}
}
return 0;
}
T2
显然可以想到二分答案。
乍一看似乎无从 check,我们尝试设 \(f_{u}\) 表示 \(u\) 的子树的盈余节点(即没有被染的),最后 check 的方式就是 \(f_1 \le 0\)。
考虑如何转移,\(u\) 的盈余节点一定是从 \(v \in son_u\) 中转移过来的。即 \(\displaystyle\sum_{v\in son_u}f_v\)。
然后就是当 B 在 \(u\) 时,A 会怎么操作,A 当然会将 \(u\) 的子节点能染得都染了,如果还有多就染其他节点(反正肯定是 \(u\) 的子树),所以还有加上一个 \(|son_u|-k\)。
注意此时盈余节点可能为负数,然而儿子转移给父亲时要与 \(0\) 取 \(\max\)。
My Code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+8;
inline int read() {
int s=1,a=0;
char c=getchar();
while(!isdigit(c)) {
if(c=='-') s=-s;
c=getchar();
}
while(isdigit(c)) {
a=a*10+c-'0';
c=getchar();
}
return s*a;
}
int n,f[N];
vector <int> G[N];
void dfs(int u,int fa,int k) {
int cnt=0;
for(auto v:G[u]) {
if(v==fa) continue;
dfs(v,u,k);
f[u]+=max(f[v],0);
cnt++;
}
f[u]+=cnt-k;
}
bool check(int k) {
memset(f,0,sizeof(f));
dfs(1,0,k);
return f[1]<=0;
}
int main() {
n=read();
for(int i=1; i<n; i++) {
int u=read(),v=read();
G[u].push_back(v);
G[v].push_back(u);
}
int l=0,r=n,ans=n;
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid)) r=mid-1,ans=mid;
else l=mid+1;
}
printf("%d\n",ans);
return 0;
}
T3
还是考虑先二分一个 \(r\)。
然后考虑 check,设 \(dp_{i,j}\) 表示前 \(i\) 个,能炸毁 \(j\) 个的概率,直接转移即可。
My Code
#include<bits/stdc++.h>
#define x1 fuck
#define x2 fukc
#define y1 aaaa
#define y2 oyyl
using namespace std;
const int N=500;
const double eps=1e-8;
inline int read() {
int s=1,a=0;
char c=getchar();
while(!isdigit(c)) {
if(c=='-') s=-s;
c=getchar();
}
while(isdigit(c)) {
a=a*10+c-'0';
c=getchar();
}
return s*a;
}
int n,a[N],k,pp,x[N],y[N];
double pq,p[N],dp[N][N];
double dis(int x1,int x2,int y1,int y2) {
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
bool check(double r) {
for(int i=1; i<=n; i++) {
double d=dis(x[i],x[0],y[i],y[0]);
if(d<=r) p[i]=1.0;
else p[i]=exp(1-d*d/r/r);
}
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1; i<=n; i++) {
dp[i][0]=dp[i-1][0]*(1-p[i]);
for(int j=1; j<=i; j++) {
dp[i][j]=dp[i-1][j]*(1-p[i])+dp[i-1][j-1]*p[i];
}
}
double ans=0;
for(int i=k; i<=n; i++) ans+=dp[n][i];
// printf("%.6f\n",ans);
return ans>=pq;
}
int main() {
n=read();
k=read(),pp=read();
pq=1.0*(1000.00-1.0*pp)/1000.00;
// printf("%.6f\n",pq);
x[0]=read(),y[0]=read();
for(int i=1; i<=n; i++) {
x[i]=read(),y[i]=read();
}
double l=0,r=1e5,ans=0;
while(r-l>=eps) {
double mid=(l+r)/2.0;
if(check(mid)) r=mid,ans=mid;
else l=mid;
}
printf("%.6f",ans);
return 0;
}
T4
先将每一行每一列分别排序,然后把同行同列权值相同的点合并,然后将相邻的点连边,用一个超级源点跑拓扑排序,求出每个点的最长路即是答案。
考虑为什么能这么做,首先,同一行或者同一列权值相同的点最后的答案肯定是一样的,可以合并之后一起考虑。
其次,我们要让最大值最小,那么从最小值到最大值就不要有空闲的数,这个我们考虑求出最小值和最大值之间最多差多少个数,这个就是有向图中的最长路。
至于超级源点,你可以把它看做 \(0\)。
主要是为了方便 bfs。
My Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+8;
inline int read() {
int s=1,a=0;
char c=getchar();
while(!isdigit(c)) {
if(c=='-') s=-s;
c=getchar();
}
while(isdigit(c)) {
a=a*10+c-'0';
c=getchar();
}
return s*a;
}
int n,m,nodcnt,s,deg[N],bfn[N],bfcnt;
struct node {
int val,id;
bool operator <(const node &bb) const {
return val<bb.val;
}
};
int getpos(int x,int y) {
return (x-1)*m+y;
}
vector <node> a[N],b[N];
vector <int> G[N];
queue <int> qq;
class UFS {
private:
int fa[N],siz[N];
public:
void init() {
for(int i=1; i<=n*m; i++) fa[i]=i,siz[i]=1;
}
int find(int x) {
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y) {
int fx=find(x),fy=find(y);
if(fx==fy) return;
if(siz[fx]<siz[fy]) swap(fx,fy);
siz[fx]+=siz[fy];
fa[fy]=fx;
}
} dsu;
void work(vector <node> c[N],int w,int h) {
for(int i=1; i<=w; i++) {
sort(c[i].begin()+1,c[i].end());
for(int j=1; j<=h; j++) {
if(c[i][j].val==c[i][j-1].val) dsu.merge(c[i][j].id,c[i][j-1].id);
}
}
}
void mkeg(vector <node> c[N],int w,int h) {
for(int i=1; i<=w; i++) {
sort(c[i].begin()+1,c[i].end());
for(int j=1; j<=h; j++) {
// printf("%d ",c[i][j].val);
if(c[i][j].val!=c[i][j-1].val&&j>1) G[dsu.find(c[i][j-1].id)].push_back(dsu.find(c[i][j].id)),deg[dsu.find(c[i][j].id)]++/*,cout<<c[i][j-1].id<<" "<<c[i][j].id<<endl*/;
}
// puts("");
}
}
int main() {
n=read(),m=read();
dsu.init();
for(int i=1; i<=n; i++) {
a[i].push_back({0,0});
for(int j=1; j<=m; j++) {
int x=read();
nodcnt++;
a[i].push_back({x,getpos(i,j)});
}
}
for(int i=1; i<=m; i++) {
b[i].push_back({0,0});
for(int j=1; j<=n; j++) {
b[i].push_back(a[j][i]);
}
}
work(a,n,m);
work(b,m,n);
mkeg(a,n,m);
mkeg(b,m,n);
s=n*m+1;
for(int i=1; i<=n*m; i++) {
if(dsu.find(i)==i) {
// assert(0);
G[s].push_back(i),deg[i]++;
}
}
qq.push(s);
while(qq.size()) {
int x=qq.front();
qq.pop();
for(auto v:G[x]) {
// assert(0);
deg[v]--;
bfn[v]=max(bfn[x]+1,bfn[v]);
// cout<<x<<" -> "<<v<<" by "<<bfn[x]<<endl;
if(!deg[v]) {
qq.push(v);
}
}
}
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
// cout<<dsu.find(getpos(i,j))<<" ";
printf("%d ",bfn[dsu.find(getpos(i,j))]);
}
puts("");
}
return 0;
}
T5
首先这是个很经典的莫反形式,简单写一下:
直接整除分块时间复杂度 \(O(klogn\sqrt{k})\) 的,过不了。
考虑差分。
设 \(i\) 的答案为 \(f(i)\)。
则:
考虑 \(\displaystyle\bigg( \bigg\lfloor \frac{k}{d} \bigg\rfloor ^n - \bigg\lfloor \frac{k-1}{d} \bigg\rfloor ^n \bigg)\) 什么时候会变。
当 \(d|k\) 时,\(\displaystyle\bigg\lfloor \frac{k}{d} \bigg\rfloor - \bigg\lfloor \frac{k-1}{d} \bigg\rfloor\) 会产生变化。
则枚举 \(d\),枚举倍数,时间复杂度 \(\displaystyle\sum_{d=1}\frac{n}{d} \approx k\log k\),快速幂可以预处理(否则 \(k\log n\log k\) 也不是很好过)。
最后求一下前缀和即可。
时间复杂度 \(O(k\log k + k\log n)\)。
My Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int Mod=1e9+7,N=2e6+8;
inline int read() {
int s=1,a=0;
char c=getchar();
while(!isdigit(c)) {
if(c=='-') s=-s;
c=getchar();
}
while(isdigit(c)) {
a=a*10+c-'0';
c=getchar();
}
return s*a;
}
int prime[N],notprime[N],cnt,mu[N];
int n,kk[N],k,pw[N],cf[N],f[N];
int ksm(int a,int b) {
int c=1;
while(b) {
if(b&1) c*=a,c%=Mod;
a*=a,a%=Mod;
b>>=1;
}
return c;
}
void init() {
notprime[0]=notprime[1]=1;
mu[1]=1;
for(int i=2; i<=2e6; i++) {
if(!notprime[i]) {
prime[++cnt]=i;
mu[i]=-1;
}
for(int j=1; j<=cnt&&i*prime[j]<=2e6; j++) {
if(i%prime[j]==0) {
mu[i*prime[j]]=0;
notprime[i*prime[j]]=1;
break;
}
mu[i*prime[j]]=mu[i]*mu[prime[j]];
notprime[i*prime[j]]=1;
}
}
}
signed main() {
n=read(),k=read();
init();
for(int i=1; i<=k; i++) {
pw[i]=ksm(i,n);
}
for(int d=1; d<=k; d++) {
for(int x=1; d*x<=k; x++) {
cf[d*x]=(cf[d*x]+(mu[d]+Mod)%Mod*(pw[x])%Mod)%Mod;
cf[d*x]=(cf[d*x]-(mu[d]+Mod)%Mod*pw[x-1]%Mod+Mod)%Mod;
}
}
int ans=0;
for(int i=1; i<=k; i++) {
f[i]=(f[i-1]+cf[i])%Mod;
ans=(ans+(f[i]^i)%Mod)%Mod;
}
printf("%lld\n",ans);
return 0;
}