DAY3
钟皓曦来了!
网址压缩
【问题描述】
你是能看到第一题的 friends 呢。 ——hja
众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。
网址压缩是现实世界的一个重要问题,随着网址数量的日益增长,长网址的存储已经给现如今的网络带来了巨大的压力。为了解决这个问题,网址压缩已经成为了一件迫在眉睫的事情(以上都是我瞎编的)。
更具体来说,网址压缩的目标是希望将较长的网址变为更短的网址,例如我们可以将 https://www.baidu.com 压缩为 http://sb.cn/。为了更加形式化我们的问题,
我们现在的任务是给定N个只包含小写字母的字符串,你需要输出对这N个字符串进行压缩的结果。你可以使用任意的压缩算法,但你需要保证满足如下的性质:
1、压缩的结果字符串仍然只有小写字母。
2、压缩的结果不能是空串,且长度必须要比原来的字符串短。
3、相同的字符串压缩结果必须相同,不同的字符串压缩结果必须不同。
任意满足上述条件的压缩方法都是正确的,所以你的目标就是对给定的N个字符串进行压缩。数据保证有解
【输入格式】
第一行一个整数????。
接下来????行每行一个字符串。
【输出格式】
输出????行每行一个字符串代表压缩的结果。
【样例输入】
4
jzm
bilibili
hhhhh
jzm
【样例输出】
ha
bilibil
hhhh
ha
【数据规模与约定】
对于100%的数据,???? ≤ 1000,字符串长度≤ 50。大部分测试点直接随机造
数据,小部分测试点为构造数据。
题解
全损压缩
先把所有的字符串读入,然后按照长度从小到大排序,然后一个一个压缩(a,b,c,...,z,aa,ab...这样子)
好像还有一个奇技淫巧,只需要排序之后把第一个字符删掉就可以过这道题
然鹅并没有卡掉这种做法
#include<cstdio> #include<cstdlib> #include<cstring> #include<map> #include<string> #include<iostream> #include<algorithm> using namespace std; const int maxn=1010; map<string,string> ma; string z[maxn]; int n,l=1,res[233],y[maxn]; bool cmp(int a,int b) { return z[a]<z[b]; } int main() { cin >> n; for (int a=1;a<=n;a++) { cin>>z[a]; y[a]=a; } res[1]=1; sort(y+1,y+n+1,cmp); for (int a=1;a<=n;a++) { string now = z[y[a]]; if (ma.count(now)!=0) ; else { string cur = ""; for (int a=l;a>=1;a--) cur = cur + (char)(res[a]+'a'-1); ma[now] = cur; res[1]++; for (int a=1;a<=l;a++) if (res[a]>26) { res[a]=1; res[a+1]++; } if (res[l+1]) l++; } } for (int a=1;a<=n;a++) cout << ma[z[a]] << endl; return 0; }
T2
异构体
【问题描述】
你是能看到第二题的 friends 呢。
——aoao
众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。
Paradeus 是一个新兴的宗教组织,该组织包含了???? − 1个 Nyto,以及一个Mercurows 总共????个人组成。每个 Nyto 都是被其他某个人传教而进入的 Paradeus,而 Mercurows 是宗教的创立者,也就是说 Mercurows 并没有被任何人拉进组织。这张记录了每个人是由谁拉进传销组织的记录被视为 Paradeus 的教义,一直被广为传颂。
然而,随着岁月的流逝,有不法分子开始对 Paradeus 的教义发动了攻击。不法分子在 Paradeus 的教义上添加了一条记录(????, ????),代表????是由????介绍入教的。这条记录的加入导致 Nyto 们发现教义已经不合法了。为了复兴教义,教徒们决定找到这条被不法分子加入的记录,并将其删除以恢复教义的荣光。更具体的说,现在给定????对记录(????????, ????????)代表是????????将????????拉入教的。注意这????条记录包含了被不法分子添加的那一条。现在我们希望你找到某一条记录,使得删掉这条记录之后剩下的???? − 1条记录能够形成合法的教义。要注意的是,教义并没有标注谁是 Mercurows,所以任何人都有可能是 Mercurows。
一棵外向树(所有边从根向外指),加一条边,找出这条边让他编号尽可能大
【输入格式】
第一行一个数????代表人数。接下来????行每行两个数????????, ????????代表一条记录。
【输出格式】
一行一个数代表删掉第几条记录能够使得教义合法。如果有多种方案,输出编号最大的方案。数据保证有解。
【样例输入】
3
1 2
1 3
2 3
【样例输出】
3
【数据规模与约定】
对于40%的数据,???? ≤ 1000。
对于另外20%的数据,可能成为 Mercurows 的人一定只有一个。
对于100%的数据,1 ≤ ???? ≤ 10^5。
题解:
讨论
1.知道根节点是谁 看看有没有入度为0的点
横叉边和返祖边
实际上是一种情况,就是有一个点入度为2
暴力删边两次看看连不连通(然而我直接贪心取了最大的,然后100-->80)
2.不能找到一个入度为0的点 -->一定有环
在这个环上删除任何一个边都可以
怎么找环?BFS,并查集,tarjan
#include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; const int maxn=100010; int n,en,head,tail,in[maxn],ex[maxn][2],q[maxn],f[maxn],pre[maxn][2]; bool use[maxn]; struct edge { int s,e,p; edge *next; }*v[maxn],ed[maxn]; void add_edge(int s,int e,int p) { en++; ed[en].next=v[s];v[s]=ed+en;v[s]->e=e;v[s]->p=p; in[e]++; ex[en][0]=s;ex[en][1]=e; } int get(int p) { if (f[p]==p) return p; else return get(f[p]); } void init() { en=0; memset(v,0,sizeof(v)); memset(in,0,sizeof(in)); } void read() { scanf("%d",&n); for (int a=1;a<=n;a++) { int s,e; scanf("%d%d",&s,&e); add_edge(s,e,a); } } bool work1(int p,int d) { memset(use,false,sizeof(use)); use[p]=true; head=tail=1; q[1]=p; for (;head<=tail;) { int p=q[head++]; for (edge *e=v[p];e;e=e->next) if (e->p!=d) { if (use[e->e]) return false; use[e->e]=true; pre[e->e][0]=p; pre[e->e][1]=e->p; q[++tail]=e->e; } } for (int a=1;a<=n;a++) if (!use[a]) return false; return true; } int work2(int p) { memset(use,false,sizeof(use)); use[p]=true; head=tail=1; q[1]=p; for (;head<=tail;) { int p=q[head++]; for (edge *e=v[p];e;e=e->next) { if (use[e->e]) return e->p; use[e->e]=true; q[++tail]=e->e; } } return -1; } int work() { for (int a=1;a<=n;a++) if (in[a]==2) { int p1=-1,p2=-1; for (int b=1;b<=n;b++) if (ex[b][1]==a) { if (p1==-1) p1=b; else p2=b; } if (p1>p2) swap(p1,p2); for (int b=1;b<=n;b++) if (in[b]==0) { if (work1(b,p2)) return p2; else return p1; } } for (int a=1;a<=n;a++) if (in[a]==0) return work2(a); for (int a=1;a<=n;a++) f[a]=a; for (int a=1;a<=n;a++) { int f1=get(ex[a][0]); int f2=get(ex[a][1]); if (f1==f2) { work1(ex[a][1],a); int p=ex[a][1],ans=a; while (p!=ex[a][1]) { ans=max(ans,pre[p][1]); p=pre[p][0]; } return ans; } f[f1]=f2; } return -1; } int main() { init(); read(); int p=work(); printf("%d\n",p); return 0; }
我的菜鸡80分代码(我的找环是在开始建了双向边,然后直接dfs,但是在上面的第一种情况直接取了max导致80pts)
#include<cstdio> #include<iostream> #include<cstdlib> #include<iomanip> #include<cmath> #include<cstring> #include<string> #include<algorithm> #include<queue> using namespace std; const int N=100005; int n; int head[N],ecnt,ver[N]; struct edge { int to,nxt; }edg[N<<1]; inline void add(int u,int v) { edg[++ecnt].to=v; edg[ecnt].nxt=head[u]; head[u]=ecnt; edg[ecnt+n].to=u; edg[ecnt+n].nxt=head[v]; head[v]=ecnt+n; } int in[N],out[N]; bool flag=0; void checkout() { for(int i=1;i<=n;i++) { if(out[i]>1) { int maxn=-1; for(int j=head[i];j;j=edg[j].nxt) { if(j<=n) maxn=max(maxn,j);//这里不对,应该删掉每一条边之后判断连通性 } printf("%d\n",maxn); flag=1; } } } bool huan[N]; bool vis[N]; int dfn[N]; int fa[N]; int cnt,sum; int ans[N]; void dfs(int now) { dfn[now]=++cnt; for(int i=head[now],v;i;i=edg[i].nxt) { v=edg[i].to; if(v==fa[now]) continue; if(dfn[v]) { //if(dfn[v]<dfn[now]) continue; ans[++sum]=v,huan[v]=1; for(;v!=now;v=fa[v]) ans[++sum]=fa[v],huan[fa[v]]=1; } else fa[v]=now,dfs(v); } } void checkhuan() { dfs(1); } int zhx=-1; void getans(int x) { for(int i=head[x];i;i=edg[i].nxt) { if(i<=n) { int v=edg[i].to; if(huan[v]==1&&vis[v]==0) { vis[v]=1; zhx=max(zhx,i); getans(v); } } } } int main() { // freopen("remove.in","r",stdin); // freopen("remove.out","w",stdout); scanf("%d",&n); for(int i=1,u,v;i<=n;i++) { scanf("%d%d",&u,&v); add(v,u); in[u]++; out[v]++; } checkout(); if(flag==1) return 0; checkhuan(); getans(ans[1]); for(int i=1;i<=sum;i++) printf("%d ",ans[i]); } /* 5 5 2 1 2 2 3 3 1 2 4 */
T3
给大佬递茶
【问题描述】
你是能看到第三题的 friends 呢。
——laekov
众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。
自古以来,递茶就是一种传统美德,而给大佬递茶更是广泛活跃于表情包的存在。现在为了模拟给大佬递茶的工作,Alice 和 Bob 开始了递茶操作。一开始Alice 和 Bob 都有一个杯子里面装了????吨的茶。现在每次 Alice 会等概率地随机向垃圾桶里面倒入4????, 3????, 2????或者????吨的茶,并且如果 Alice 倒了????吨的茶,Bob 就会向垃圾桶里面导入4????−????吨的茶。注意每次操作的时候 Alice 或者 Bob 的茶有可能不够多,这个时候就能倒多少到多少。现在问 Alice 在四种操作完全等概率的情况下,Alice 先把自己的茶倒光的概率加上 Alice 和 Bob 同时把茶倒光的概率的一半是多少。注意,Alice 和 Bob 每轮倒茶都是同时开始同时结束的。
【输入格式】
第一行两个整数????,????。
【输出格式】
输出一行一个六位实数代表答案。
【样例输入】
2 1
【样例输出】
0.625000
【数据规模与约定】
对于30%的数据,1 ≤ ????,???? ≤ 100。
对于60%的数据,1 ≤ ????,???? ≤ 1000。
对于另外20%的数据,???? = 1。
对于100%的数据,1 ≤ ????,???? ≤ 10^9。
题解
首先,k其实没有区别
N=10,k=5和n=2,k=1是一样的
把读进来的n变成n/k(上取整),k变成1
给你一个数,问另外一个数(要注意这种问题可以通过某种玄学的方式找规律)
打表???
1. 爆搜
可以加上记忆化 f[i][j]表示a里面有i,b里面有j的概率是多少
最后求f[n][n]
F[0][j]=1,f[i][0]=0,f[0][0]=0.5
转移 f[i][j]=0.25*(f[i-4][j]+f[i-3][j-1]+f[i-2][j-2]+f[i-1][j-3]) 70pts
复杂度n^2
打表:随着n的增长,概率在增长
还有:答案只要求保留六位小数
所以,当n>?时 只需要输出1.000000??????
什么玄学东西????
#include<cstdio> #include<cstdlib> #include<cstring> using namespace std; const int maxn=201; int n,k; double f[maxn][maxn]; bool g[maxn][maxn]; double dfs(int n,int m) { if (n<=0 && m<=0) return 0.5; if (n<=0) return 1.0; if (m<=0) return 0.0; if (g[n][m]) return f[n][m]; g[n][m]=true; for (int a=1;a<=4;a++) f[n][m]+=0.25*dfs(n-a,m-4+a); return f[n][m]; } double work(int n,int k) { if (n/k>=maxn) return 1.0; return dfs((n/k)+(n%k?1:0),(n/k)+(n%k?1:0)); } int main() { scanf("%d%d",&n,&k); printf("%.6lf\n",work(n,k)); return 0; }
给大佬递茶
【问题描述】
你是能看到第三题的 friends 呢。
——laekov
众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没
什么关系。
自古以来,递茶就是一种传统美德,而给大佬递茶更是广泛活跃于表情包的
存在。现在为了模拟给大佬递茶的工作,Alice 和 Bob 开始了递茶操作。一开始
Alice 和 Bob 都有一个杯子里面装了????吨的茶。现在每次 Alice 会等概率地随机向
垃圾桶里面倒入4????, 3????, 2????或者????吨的茶,并且如果 Alice 倒了????吨的茶,Bob 就
会向垃圾桶里面导入4????− ????吨的茶。注意每次操作的时候 Alice 或者 Bob 的茶有
可能不够多,这个时候就能倒多少到多少。现在问 Alice 在四种操作完全等概率
的情况下,Alice 先把自己的茶倒光的概率加上 Alice 和 Bob 同时把茶倒光的概
率的一半是多少。注意,Alice 和 Bob 每轮倒茶都是同时开始同时结束的。