• 寒假模你赛题解


    寒假模你赛题解


    1.13

    平凡的函数

    发现长得很像欧拉筛,我们考虑怎么筛

    在我们第一次找到某个质数p时显然有 \(F[p]= p \otimes 1\)

    在筛的过程中考虑如何计算\(F[i*prime[j]]\)的值

    因为有

    \[F(a*b)=F(a)*F(b),(a与b互质) \]

    即互质积性

    那么当\(prime[j] \nmid i\)时直接相乘更新

    当$prime[j] \mid i \(时,\)prime[j]\(是\)i\(的最小质因数,设将\)i\(的\)prime[j]\(因数全部拿掉后为\)a\(,求出\)i\(的质因数分解下,\)prime[j]\(的指数再加上一为\)b$,

    那么此时\(a\)\(prime[j]\)显然是互质的,可以积性求了,即

    \[F[i*prime[j]]=F[a]*(p \otimes b) \]

    考场上卡了一会儿

    code
    
    
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <cmath>
    #define ll long long
    using namespace std;
    
    const int maxn=5e7+10,maxm=6e6+10;
    
    bool vis[maxn];
    int prime[maxm],cnt;
    int cs[maxn];
    int n;
    ll ans;
    
    void get(int n){
    	ans=cs[1]=1;
    	for(int i=2;i<=n;i++){
    		if(!vis[i]){
    			prime[++cnt]=i;
    			cs[i]=i^1;
    		}
    		for(int j=1;j<=cnt && i*prime[j]<=n;j++){
    			vis[i*prime[j]]=true;
    			if(i%prime[j]==0){
    				int res=i,bit=1;
    				while(res%prime[j]==0){res/=prime[j];bit++;}
    				cs[i*prime[j]]=(prime[j]^bit)*cs[res];
    				break;
    			}else cs[i*prime[j]]=cs[i]*cs[prime[j]];
    		}
    		ans+=cs[i];
    	}
    
    }
    
    int main (){
    	freopen("func.in","r",stdin);
    	freopen("func.out","w",stdout);
    	scanf("%d",&n);
    	get(n);
    	printf("%lld\n",ans);
    }
    
    

    那一天她离我而去

    考场上没什么思路确实,只有极其暴力的想法,题解里提到的暴力也没想到.考场上判了个一号点所在的环,在环上暴力dfs骗了点分

    正解:

    首先有一个暴力的想法
    我们发现这条回路可以分为三部分

    1.从1到达x点的距离
    2.从x点到达y点的距离
    3.从y点回到x点的距离
    

    选出一组\(x\)\(y\)时,1和3就是确定的,我们要求的就是\(x\)\(y\)之间的最短路

    暴力跑的话要算\(n\)次最短路,我们考虑如何优化这个算法

    首先剥离出一个点集为直接与一号点相连的点,一个边集为与一号点相连的边

    我们暴力的思路实际上就是每次选出一组起点和终点求最短路,那如果我们一次求多组呢?

    形象化的来说,我们每次从点集中选一些点作为\(x\)点,一些点作为\(y\)点,我们将边集中\(1\)连向\(x\)点的边加入图中(单向),再建立一个超级汇点,将\(y\)点连向\(1\)的边接到超级远点上,再以\(1\)为原点就最短路,实际上我们就是求出来从\(1\)经过某\(x\)点再经过某\(y\)点回到一的最短路,也就是题目要求的最小环.

    暴力中我们要选出任意两点作为起点和终点才能求出最终答案,那么我们如何分组才能同时保证时间复杂度和正确性呢?

    就挺神的,我们考虑每个点编号的二进制表示,任意两点的编号不同,二进制下任意两点编号至少有一位不同,那我们枚举二进制下的每一位,为\(1\)的分一组,为\(0\)的分一组,这样就保证了任意两点至少在某一次被分为了起点和终点,我们也只需要跑\(log(n)\)次最短路

    分组实在是太巧妙了

    点击查看代码
    
    
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <algorithm>
    #include <queue>
    using namespace std;
    
    const int maxn = 100005;
    
    struct edge{
    	int to, next, dis;
    }e[maxn*2];
    
    struct node{
    	int dis,pt;
    	node(){}
    	node(int a,int b):dis(a),pt(b){}
    	bool operator<(const node &rhs)const{
    		return dis>rhs.dis;
    	}
    };
    
    int head[maxn*2], len;
    void lqx(int from, int to, int dis){
    	e[++len].to = to;
    	e[len].next = head[from];
    	e[len].dis = dis;
    	head[from] = len;
    }
    struct nodes{
    	int to, val;
    }a[maxn];
    
    queue<int> q;
    bool vis[maxn];
    int dis[maxn];
    
    void dij(int u){
    	memset(dis,0x3f,sizeof(dis));
        priority_queue < node >q;
        memset(vis,0,sizeof(vis));
    	dis[u]=0;
    	q.push(node(0,u));
    	while(!q.empty()){
    		u=q.top().pt;
    		q.pop();
    		if(vis[u])continue;
    		vis[u]=true;
    		for(int i=head[u];i;i=e[i].next){
    			int v=e[i].to,d=e[i].dis;
    			if(dis[v]>dis[u]+d){
    				dis[v]=dis[u]+d;
    				//printf("%d %d %d %d\n",v,dis[v],u,dis[u]+d);
    				q.push(node(dis[v],v));
    			}
    		}
    	}
    }
    
    int cnt;
    int tmp[maxn*2];
    int T,n,m;
    
    void solve(){
        memset(head, 0, sizeof head);
        memset(vis, 0, sizeof vis);
        memset(dis, 0x3f, sizeof dis);
        len=cnt=0;
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= m; i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            if(u > v) swap(u, v);
            if(u == 1) a[++cnt].to = v, a[cnt].val = w;
            else lqx(u, v, w), lqx(v, u, w);
        }
        int ans = 0x3f3f3f3f;
        memcpy(tmp, head, sizeof head);
        for(int i = 0; (1 << i) <= cnt; i++){
            memcpy(head, tmp, sizeof tmp);
            for(int j = 1; j <= cnt; j++){
                if(j & (1 << i)) lqx(1, a[j].to, a[j].val);
                else lqx(a[j].to, n + 1, a[j].val);
            }
            dij(1);
            ans = min(ans, dis[n + 1]);
        }
        if(ans == 0x3f3f3f3f) ans = -1;
        printf("%d\n", ans);
    
    }
    
    int main(){
    	freopen("leave.in", "r", stdin);
    	freopen("leave.out", "w", stdout);
    	scanf("%d",&T);
    	while(T--)solve();
    	return 0;
    }
    
    

    熟练剖分

    期望神题,半个小时题没读懂样例没明白

    我们定义\(F[i][j]\)表示节点\(i\)的子树中,最坏时间复杂度不超过j的概率

    考虑如何转移

    先不转移了


    1.15

    匹配

    暴力\(KMP\)即可

    但是注意到\(KMP\)求出的最长boader是不考虑自己是自己的boader的

    所以要特判!!!!!!!!!!!

    如果加入的字符和原位置一样直接输出答案即可

    所以特判有八十分

    所以垃圾数据毁我青春

    回家

    看到这个题第一反应就是找个割点就了了

    但是眉头一皱发现不对劲
    怎么可能这么水

    我们发现必经点一定是割点(这很显然),但是割点不一定是必经点

    因为割点虽然会把图分为两个连通块,但是如果\(1\)\(n\)仍在一个连通块呢

    还得求割点,但是在求割点的过程中,我们判断一下某个割点的儿子是否能搜到\(n\)点,如果可以那么这个割点属于答案

    寿司

    考虑断环成链

    我们发现合法解中,一定有一个边界,这个边界的左右不发生交换,这也就是断环成链,选取的左右边界就是这个边界

    同时发现我们只需要考虑一种颜色的移动,另一种颜色会同时完成的.

    那么就考虑\(R\)的移动步数

    现在\([1,2*n]\)中选出断环成链的左右边界\(L,R\),我们只需要将\(R\)全都移到左右边界就行

    发现存在一个分界点,这个分界点左侧的\(R\)都向左移动,右侧都向右移动,并且随着\(L,R\)向右移动,这个分界点也只向右移动

    那么我们就可以预处理出这个分界点的一些值,进行求答,考虑每次移到\(L\)\(R\)的开销即可

    时间复杂度\(O(n)\),常数略大

    也可以考虑把所有\(R\)都移到中间(就相当于把所有\(B\)移到两边)

    合理的预处理可以减小常数(大概就是\(1000ms\)\(2000ms\)的差距)


    1.16

    斐波那契

    原题

    某个数的父亲就是他减去离他最近的斐波那契数,二分暴力跳父亲即可

    数颜色

    原题

    考虑到只交换相邻两个兔子,而且兔子除了颜色没有其他区别,也就是交换两个颜色相同的兔子相当于没交换,同种颜色的兔子的相对位置不变

    那么我们就可以考虑用vector保存每种颜色的下标集合,每次在vector里二分查找修改即可

    复杂度\(O(nlogn)\)

    注意有些颜色可能是不存在的,特判一下,要不然不知道二分出来什么鬼东西,奇奇怪怪的RE

    分组

    \(K=1\) 时暴力判合法即可

    \(K=2\) 时用扩展域并查集维护关系,同时需要特判 \(2*a[i]=x^2\)的情况


    1.16夜间版

    中国象棋

    \(50%\)的数据可以三进制状态压缩求

    然后我们考虑如何优化这个状压

    发现行与行之间,列与列之间时不相互影响的,并且在转移过程中,一些转移本质上,数值上也是相同的

    按套路我们依旧状压一行一行的放,显然一行只能放小于等于\(2\)个炮,炮可以放在还没有放炮的某列或者之前只放了一个炮的某列,在同一性质的列上,我们放在他们中的哪一个都是一样的,直接组合意义计算

    那么就比较清晰了

    我们定义\(F[i][j][k]\)为当前放完了第\(i\)行,有\(j\)列没放跑,有\(k\)列只放了一个炮的方案数

    枚举放置的情况转移即可
    记得限制边界

    奇妙的Fibonacci

    太奇妙了

    这又是个欧拉筛

    通过大量打表发现

    \(F_j \mid F_i\)\(j \mid i\)


    1.18

    入阵曲

    丹青千秋酿,一醉解愁肠。
    无悔少年枉,只愿壮志狂。
    

    有显然的\(n^4\)做法,直接二维前缀和再枚举子矩阵

    我们考虑如何优化这个过程

    发现我们求某个矩阵的和时,是用一个大矩阵减去几个小矩阵,试图变换柿子突破

    假如只有矩阵只有一行,那么我们\(n^2\)枚举矩阵的左右边界\(L,R\)

    \(ans+= (pre[R]-pre[L-1] \% K==0)\)

    也就是有 \(K \mid (pre[R]-pre[L-1])\)

    这不是同余的充要条件吗

    我们考虑扩展到多行,实际上我们就可以把多行视为一行,每次枚举当前要考虑多少行,行的上下边界在哪,再把列扫一遍,记录前边的矩阵模K余数即可
    相同余数的个数就是贡献

    可以做到\(O(n^3)\)

    将军令

    历史/落在/赢家/之手
    至少/我们/拥有/传说
    谁说/败者/无法/不朽
    拳头/只能/让人/低头
    念头/却能/让人/抬头
    抬头/去看/去爱/去追
    你心中的梦
    

    .

    又想起了四月。
    如果不是省选,大家大概不会这么轻易地分道扬镳吧?
    只见一个又一个昔日的队友离开了机房。
    凭君莫话封侯事,一将功成万骨枯。
    

    发现我们在任何一个点放的开销时完全相同的,那么我们可以试图贪心让每个开销的贡献更大

    显然我在某个更靠近中央的点放小队,他能扩散的点比边缘点更多,但是我又必须扩散到边缘点,那么我每次都考虑放在一个尽量靠近中央的,且能控制到当前离根最远的点 的店

    也就是放在当前深度最大的点的\(K\)级祖先,再从这个点向外暴力扩展\(K\)层打标记即可

    显然第一眼看题就觉得是像套路的树形dp,但是细想好像不太好写

    然后就思考如何偏分

    星空

    命运偷走如果只留下结果,
    时间偷走初衷只留下了苦衷。
    你来过, 然后你走后, 只留下星空。
    

    .

    逃不掉的那一天还是来了,小 F 看着夜空发呆。
    天上空荡荡的,没有一颗星星?D?D大概是因为天上吹不散的乌云吧。
    心里吹不散的乌云,就让它在那里吧,反正也没有机会去改变什么了。
    

    我们发现这题长得有点像分手,如何我们就考虑如何优雅分手

    \(m=1\)的时就是分手,直接\(O(n)\)扫一遍,暴力反转就行

    如何我们开始审视测试点
    发现我们可以大力骗分

    要求最小步数,第一发写的二分check,二分确实没太大问题但是我写挂了,就思考如何优雅bdfs

    那么就迭代加深,每次限定最小答案,接着分手,只不过要多枚举一下用哪个长度的操作点亮当前点,暴力剪枝搜

    取得了72分好成绩

    好了开正解

    挺离谱的想不到

    我们思考如何转化这个问题,发现我们用某一个长度操作摁两次,两次区间差一,可以做到将某一段的两端翻转
    好像没用?

    考虑差分,对一段的反转操作就是反转两个边界,

    不会写了


    1.19 理科综合

    math

    想不到什么方向只能骗分

    考虑一个数的若干倍模k可以出现若干个余数,我们把这些余数都求出来,再加上之前的

    1.24 HNOI (雾

    队长快跑

    显然一眼DP,我们如何设计状态和转移

    发现某一个水晶是否能摧毁,与当前已经摧毁的\(A_i\)最小值有关,也与他的\(B\)值有关

    我们设\(F[i][j]\)为前\(i\)个水晶,摧毁的水晶最小值为\(j\)时,能够摧毁的最大数量

    考虑暴力转移,

    \(A_i \leq B_i\)时,为使得能够摧毁,之前摧毁的最小值取值区间为\([B_i+1,MAX]\),在前\(i-1\)中找最大的转移

    \(A_i > B_i\)时,最小值取值区间为\([B_i+1,MAX]\),有两种转移

    \[F[i][A_i]=max\{F[i-1][j]\}+1,A_i \leq j \leq MAX \]

    \[F[i][j]=F[i-1][j]+1,B_i<j<A_i \]

    暴力枚举为\(O(n^3)\),转移时继承上一个位置可以做到\(O(n^2)\)

    但是我们发现转移就是求一个之前的最大值,再对某一个区间进行区间加,所以我们可以把第二维丢到线段树上,第一维丢掉,从头到尾一棵线段树即可,就相当于继承上一阶段的状态

    注意转移的先后顺序

    影魔

    一个神奇的转化

    如果不考虑深度的限制,那么就是求某棵子树内颜色的个数

    第一反应是线段树合并,但是好像并不太ok太ex了

    然后就伪了一个树上莫队,然后就拿到了和dfs一样的分数

    下面说正解

    主席树

    不考虑深度限制,每个点能够产生贡献在它到根的路径上,在树上做类似差分的做法

    	u+1;
    	lca(pre,u)-1
    	lca(u,suf)-1
    	lca(pre,suf)+1
    

    pre和suf为u点在相同颜色dfn序上的前驱和后继

    容斥以消除重复贡献,将问题转化为求子树和,p

    由于有深度限制,那么按深度建主席树,限制深度求即可

    本题不强制在线,所以还有一种线段树合并加树状数组的离线做法,但是没写也没胡

    抛硬币

    可能是比较水的题

    \(F[i][j]\)为前i位长度为j的本质不同子序列个数

    考虑每个字符新加进串时产生的贡献
    \(F[i-1][j-1]\),同时它还可以直接继承之前的串\(F[i-1][j]\)

    本质不同也就是子序列至少有一位不同,考虑新加进来的这个字符的重复贡献,假设之前没有出现过这个字符,那么它新产生的贡献一定不重,如果之前已经出现过,记上一次出现的位置为\(last\),那么会重复产生\(F[last-1][j-1]\)的贡献,我们将这部分减去即可


    1.25

    Reverse

    这很像星空的一个子问题,但是星空那题的步长只有64种,而本题的步长可以有\(n\)种,所以直接暴力bfs的做法显然会炸,我们考虑如何优化这个算法

    可以线段树优化建图但是不会写也不会调,我们尝试换一种方法乱搞

    发现在bfs过程中,每个状态第一次抵达的步数就是最小值,那么我们之后再枚举更新它是无意义的,我们也只需要从他出发更新其他状态一次.

    两种做法,枚举过程中,我们是两个连个跳的,所以我们可以对可达点按奇偶分组,分别放在两个set里,每次从set里lower_bound当前点能够翻到的区间[l,r],找到一个更新一个,erase一个,时间复杂度为\(O(nlogn)\)

    另一种做法是维护链表,复杂度为\(O(n)\)但是不如set省事

    silhouette

    大概就是组合数学容斥二项式反演,然而数学都不会暂时先鸽了

    Seat

    神仙概率DP

    模拟一下选座过程,我们可以发现第\(i\)个人选座时题目定义的距离时一定的,不管前面的人怎么坐,同时选了一些人后的区间局面本质也相同

    我们考虑按照题目中定义的距离分层处理,我们只需要知道每个人有多少概率坐在一个奇区间/偶区间,考虑DP

    \(dp[i][j]\)表示这一层已经做下\(i \)个人后,还有\(j\)个偶区间的概率,转移当前人坐在了那种区间,转移后算出这个人的答案
    通过注释std的方式具体讲

    //注意
    //std中odd为偶数,even为奇数
    //接下来所说的最小距离为题目中定义的距离
    #include<bits/stdc++.h>
    using namespace std;
    const int N=1030;
    int n,mod;
    int qpow(int x,int k,int ans=1){
        while(k){
            if(k&1) ans=ans*x%mod;
            x=x*x%mod;
            k>>=1;
        }
        return ans;
    }
    //dp为最终答案矩阵
    //f为题解中定义的dp数组(已经坐了i人,还剩j个偶区间),g为一个中间临时数组
    //cnt[i]为最小距离为i的人的数量,也就是对距离分层
    //odd[i]为最小深度为i的偶区间的数量
    //pos[i]为第i个人坐的位置
    int dp[N][N],f[N][N],g[N][N],vis[N],inv[N],cnt[N],odd[N],pos[N];
    int main(){
        scanf("%d%d",&n,&mod);
        for(int i=1;i<=n;++i) inv[i]=qpow(i,mod-2);
        vis[0]=vis[n+1]=true;
        for(int i=1;i<=n;++i){
            int pl=0,pr=0,mx;
            for(int j=0;j<=n;++j){
                int r=j+1;
                while(!vis[r]) ++r;
                if(r-j>pr-pl) pl=j,pr=r;
                j=r-1;
            }
            ++cnt[mx=pr-pl>>1]; odd[mx]+=pr-pl&1;
            pos[i]=pl+mx; vis[pl+mx]=true;
        }
    	/*------------
    	本段预处理可以理解为找出一种可行的座位安排,同时求得各层的人数,偶区间数量
    	---------------*/
    
    
        int sum=n;//sum是当前还没有坐的人数
        for(int i=1;i<=n;++i){//最小距离从小到大枚举,也就是逆推
            if(!cnt[i]) continue;
            int l=sum-cnt[i]+1,r=sum;//最小距离为i的人是我们预处理的一组解中的[l,r]
            if(i==1) for(int j=l;j<=r;++j) for(int k=l;k<=r;++k) dp[j][pos[k]]
    		=inv[cnt[i]];
    		//显然最小距离为一时,剩下的人到每个位置的概率时相同的,直接均分概率
            else{
                for(int j=0;j<=cnt[i];++j) for(int k=0;k<=odd[i];++k) f[j][k]=0;//清空数组
                f[0][odd[i]]=1; //还没有坐人,那么该层剩下odd[i]个偶数区间的概率为1
                int p=l+odd[i]-1;
    			//偶数区间长度的最后一个人(接下来会用到),为什么?
    			//预处理时,对于某一个mx,它对应的偶区间的(pr-pl)显然要大于奇区间
    			//也就是说这一层的前若干人都在偶区间,剩下的在奇区间
                for(int j=1;j<=cnt[i];++j){//当前坐了几个人
                    int oddw=0,evenw=0;//odd为偶数 even为奇数
                    for(int k=0;k<=odd[i];++k){//还剩多少偶区间
                        if(!f[j-1][k]) continue;
    					//dp转移 所以f值为0的时候可以剪枝 k表示剩余多少个偶数区间
                        int frac=(cnt[i]-(j-1))+k,w=0;
    					//括号内表示剩余的区间个数 +k表示剩余多少个转移点
                        if(k){//还有偶区间剩余
                            w=f[j-1][k]*k*2%mod*inv[frac]%mod;
    						//占一个偶区间位置 那么概率为k*2/转移点
                            oddw=(oddw+w*inv[odd[i]*2])%mod;
    						//方便累加答案 对于这一次的转移 可能作用在不同的转移点
                            (f[j][k-1]+=w)%=mod;
                        }
                        if(cnt[i]-odd[i]){
    						//可以向奇区间转移
    						//这个判断条件也可以写为判断剩下没有奇区间
    						//还可以不写
                            w=f[j-1][k]*(frac-2*k)%mod*inv[frac]%mod;
    						//向奇数区间转移的概率
                            evenw=(evenw+w*inv[(cnt[i]+odd[i])-odd[i]*2])%mod;//向不同奇数区间转移
                            (f[j][k]+=w)%=mod;
                        }
                    }
                    for(int u=l;u<=p;++u) (dp[l+j-1][pos[u]]+=oddw)%=mod,(dp[l+j-1][pos[u]+1]+=oddw)%=mod;
                    for(int u=p+1;u<=r;++u) (dp[l+j-1][pos[u]]+=evenw)%=mod;
    				//累加答案,前面已经说明过p的意义
                }
                for(int j=l;j<=p;++j){//坐在偶区间的人
                    int L=pos[j]-i+1,R=pos[j]+i;//当前的偶区间左右端点
                    for(int v=L;v<=R;++v){
                        if(v==pos[j]) continue;//不能是选择的点
                        for(int u=r+1;u<=n;++u){//后面每一个人
                            int s=v<pos[j]?v+i+1:v-i,w=dp[u][v]*inv[2]%mod;
    						//后一个人在位置v的概率除2
                            (g[u][v]+=w)%=mod; (g[u][s]+=w)%=mod;
    						//这实际上就是题解中提到的利用对称性推答案
                        }
                    }
                    for(int v=L;v<=R;++v) for(int u=r+1;u<=n;++u) dp[u][v]=g[u][v],g[u][v]=0;
                }
            }
            sum-=cnt[i];//考虑下一层 剩余人数减少
        }
        for(int i=1;i<=n;i++,puts("")) for(int j=1;j<=n;j++) printf("%d ",dp[i][j]);
        return 0;
    }
  • 相关阅读:
    59
    58
    57
    56
    55
    54
    53
    转 Using $.ajaxPrefilter() To Configure AJAX Requests In jQuery 1.5
    jquery用正则表达式验证密码强度
    什么是高内聚、低耦合?(转载)
  • 原文地址:https://www.cnblogs.com/Delov/p/15812741.html
Copyright © 2020-2023  润新知