雅礼学习10.3c++
暴力修改+一维差分+二维差分算法
莫名其妙就没了49分。。。数组
好像是数组开的不够大?学习
这。。。几率和指望,一会不会,连那个一分的部分分都没有任何思路优化
题目并无看太懂。。spa
写了一个枚举算法,而后对某个一分的数据输出显然的结果code
。。。blog
而后就只拿了1分排序
枚举挂了,由于会错了题目含义递归
/* * 一个个修改确定超时。。 * q==0的直接输出0 * 19分应该是暴力 * * 考虑对每次操做,计算一共修改了多少个位置 * 奇数个的话就让当前答案异或这个数字 * 对于边界单独讨论 * 可是问题在于。。。若是修改的位置恰好把当前答案所在位置变大了,就不该该是异或,而是+ * 真的不会维护啊 * 伪装这么写是正确的吧。。。 * * 每行差分 * 最后O(n^2)统计 */ #include <cstdio> inline int read() { int n=0,w=1;register char c=getchar(); while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar(); return n*w; } inline int min(int x,int y) {return x<y?x:y;} const int N=1002; long long map[N][N],add[N][N],cut[N][N]; int main() { freopen("u.in","r",stdin); freopen("u.out","w",stdout); int n=read(),q=read(),r,c,s,l,x; long long ans=0; if(q==0) { printf("0"); goto E; } if(q<=400) { while(q--) { r=read(),c=read(),l=read(),s=read(); x=min(r+l-1,n); for(int i=r;i<=x;++i) for(int j=c;j<=min(i-r+c,n);++j) map[i][j]+=s; } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) ans^=map[i][j]; } else if(q<=2000) { while(q--) { r=read(),c=read(),l=read(),s=read(); x=min(r+l-1,n); for(int i=r;i<=x;++i) map[i][c]+=s,map[i][min(i-r+c,n)+1]+=-s; } for(int i=1;i<=n;++i) { x=0; for(int j=1;j<=n;++j) { x+=map[i][j]; printf("%d ",x); ans^=x; } puts(""); } } else { while(q--) { r=read(),c=read(),l=read(),s=read(); add[r][c]+=s,add[r+l][c]-=s; cut[r][c+1]+=s,cut[r+l][c+l+1]-=s; } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) { add[i][j]+=add[i-1][j]; cut[i][j]+=cut[i-1][j-1]; } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) { map[i][j]+=map[i][j-1]+add[i][j]-cut[i][j]; ans^=map[i][j]; } } /* else { while(q--) { r=read(),c=read(),l=read(),s=read(); x=l+1; if(r+l-1>n || c+l-1>n) {//想了半天没想出来怎么快速把多余的部分删掉。 x=0; for(int i=r;i<=min(r+l-1,n);++i) x+=min(i-r+c,n); } if(x&1)ans^=s; } }*/ printf("%lld",ans); E: fclose(stdin);fclose(stdout); return 0; }
/* * 几率。。。 * 根本一会不会啊。。。 * 骗分走人 */ #include <cstring> #include <cstdio> const int N=31; char s[N]; int main() { freopen("v.in","r",stdin); freopen("v.out","w",stdout); scanf("%d%d",&n,&k); scanf("%s",s); int x=0; for(int i=0;i<n;++i) if(s[i]=='W') ++x; if(k==0 || k==n) printf("%.10lf",(double)1.0*k); else printf("%.10lf",(double)x*1.0/2); fclose(stdin);fclose(stdout); return 0; }
/* * 输出0有1分。 */ #include <cstdio> #include <vector> int n,step,cnt,k_2,z_2,ku; int l_note[100023]; struct note{ int con,have,wash; }; bool dis[100023]; std::vector<note> tree[100023]; std::vector<int> kuai[100023]; void search(int num) { for(int space,i=0;i<l_note[num];i++) { space=tree[num][i].con; if(((tree[num][i].wash!=tree[num][i].have)||(tree[num][i].wash==2))&&dis[space]!=true) { tree[num][i].have=tree[num][i].wash; ++cnt; dis[num]=true; search(space); break; } } } bool vis[100023]; void qk(int num) { vis[num]=true; for (int i=0;i<kuai[num].size();++i) { int space=kuai[num][i]; if(tree[num][i].wash==2)++k_2; if(vis[space]!=true) qk(space); } } int main () { freopen("w.in","r",stdin); freopen("w.out","w",stdout); scanf("%d",&n); if(n<=1000) { for(int a,b,c,d,i=1;i<n;++i) { scanf("%d%d%d%d",&a,&b,&c,&d); tree[a].push_back((note){b,c,d}); ++l_note[a]; tree[b].push_back((note){a,c,d}); ++l_note[b]; if((c!=d)||(d==2)) { kuai[a].push_back(b); kuai[b].push_back(a); } if(d==2)++z_2; } for(int i=1;i<=n;++i) { for(int o=0;o<l_note[i];o++) { if((tree[i][o].wash!=tree[i][o].have)&&tree[i][o].wash!=2) { dis[tree[i][o].con]=true; search(i); break; } } } for(int i=1;i<=n;++i) if(vis[i]!=true&&kuai[i].size()!=0) { ++ku; qk(i); } step=ku-(z_2-k_2/2); printf("%d %d",step,cnt); } else printf("0 0"); fclose(stdin);fclose(stdout); return 0; }
第一眼\(n\times q\)作法,\(3e8\)显然不能过
那么考虑对其进行优化:差分序列的访问时不连续的,因此咱们考虑把它变成连续的
就过了。。。
官方正解:
竖着作一次差分,斜着作一次差分,那么一个三角形就能够肯定出来了,然后\(n^2\)扫一遍求异或和
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1e3+10; int n,q; ll a[maxn][maxn],b[maxn][maxn],ans; inline void add_a(int x,int y,int v){ if(x<=n&&y<=n) a[x][y]+=v; } inline void add_b(int x,int y,int v){ if(x<=n&&y<=n) b[x][y]+=v; } int main(){ freopen("u.in","r",stdin); freopen("u.out","w",stdout); scanf("%d%d",&n,&q); while(q--){ int r,c,l,s; scanf("%d%d%d%d",&r,&c,&l,&s); add_a(r,c,s); add_a(r+l,c+l,-s); add_b(r+l,c,-s); add_b(r+l,c+l,s); } for(int i=1;i<=n;++i) for(int j=1;j<=n;++j){ if(i>1) a[i][j]+=a[i-1][j-1]+a[i-1][j]-a[i-2][j-1]; else a[i][j]+=a[i-1][j-1]+a[i-1][j]; b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1]; ans^=a[i][j]+b[i][j]; } printf("%lld\n",ans); return 0; }
能够获得一个\(O(2^n\times n)\)的状压\(DP\)的作法,记录每一个球是否尚未被移除,而后按照最优策略指望移除白球数
事实上有不少重复状态,也就是剩下的求的颜色序列相同时结果是同样的
考虑将状态记成剩下的颜色序列,长度较小的时候就直接用数组去存,较大的时候用\(map\)去存
状态个数能获得一个上界是\(\sum_{i=0}^n\min\{2^i,{i\choose n}\}\)(事实上最大值为\(\sum_{i=1}^{n+1}Fib_i\)可是这一点也不\(noip\))
#include<bits/stdc++.h> using namespace std; const int maxn=30+5; int n,k; char s[maxn]; namespace ${ const int xxx=24; double a[1<<xxx+1]; map<int,double> m[maxn]; inline void init(){ for(int i=0;i<1<<xxx+1;++i) a[i]=-1; } inline bool count(int bit,int len){ if(len<=xxx) return a[1<<len|bit]!=-1; else return m[len].count(bit); } inline double&find(int bit,int len){ if(len<=xxx) return a[1<<len|bit]; else return m[len][bit]; } } inline int erase(int bit,int k){ return bit&(1<<k)-1|bit>>1&-1<<k; } inline double max_(double a,double b){ return a>=b?a:b; } double dfs(int bit,int len){ if(len<=k) return 0; if($::count(bit,len)) return $::find(bit,len); double&res=$::find(bit,len); res=0; for(int i=0,j=len-1;i<=j;++i,--j) if(i<j) res+=max_(dfs(erase(bit,i),len-1)+(bit>>i&1),dfs(erase(bit,j),len-1)+(bit>>j&1))*2; else res+=dfs(erase(bit,i),len-1)+(bit>>i&1); return res/=len; } int main(){ freopen("v.in","r",stdin); freopen("v.out","w",stdout); $::init(); scanf("%d%d%s",&n,&k,s); k=n-k; int bit=0; for(int i=0;i<n;++i) bit|=(s[i]=='W')<<i; printf("%.10f\n",dfs(bit,n)); return 0; }
若是最后翻转的边集是\(S\),最少操做数为\(\{V,S\}\)中奇数度数的点的一半,最小操做总长度是\(|S|\)
考虑树形\(DP\),\(dp[i][0/1]\)记录以\(i\)为根的子树内,\(i\)与父亲之间的边是否翻转,最少的奇数度数的点的个数,此时的最小总长度
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+10; const pair<int,int> inf=make_pair(1e9,1e9); int n; vector<pair<int,int> > g[maxn]; pair<int,int> dp[maxn][2]; inline pair<int,int> operator+ (pair<int,int> a,pair<int,int> b){ return make_pair(a.first+b.first,a.second+b.second); } void dfs(int pos,int fa,int type){ pair<int,int> tmp0(0,0),tmp1(inf); for(int i=0,v;i<g[pos].size();++i) if((v=g[pos][i].first)!=fa){ dfs(v,pos,g[pos][i].second); pair<int,int> nxt0,nxt1; nxt0=min(tmp0+dp[v][0],tmp1+dp[v][1]); nxt1=min(tmp1+dp[v][0],tmp0+dp[v][1]); tmp0=nxt0;tmp1=nxt1; } if(type==0||type==2) dp[pos][0]=min(tmp0,make_pair(tmp1.first+1,tmp1.second)); else dp[pos][0]=inf; if(type==1||type==2) dp[pos][1]=min(make_pair(tmp0.first+1,tmp0.second+1),make_pair(tmp1.first,tmp1.second+1)); else dp[pos][1]=inf; } int main(){ freopen("w.in","r",stdin); freopen("w.out","w",stdout); scanf("%d",&n); for(int i=1,a,b,c,d;i<n;++i){ scanf("%d%d%d%d",&a,&b,&c,&d); if(d!=2) d=(c!=d); g[a].push_back(make_pair(b,d)); g[b].push_back(make_pair(a,d)); } dfs(1,0,0); printf("%d %d\n",dp[1][0].first/2,dp[1][0].second); return 0; }
主要是题目只有题目
有一个\([1,2^n]\)的排列\(A\{1,\cdots,2^n\}\)
能够执行的操做有\(n\)种,每种操做最多能够执行一次。第\(i\)种操做:将序列从左到右划分为\(2^{n-i+1}\)段,每段刚好包括\(2^{i-1}\)个数,而后总体交换其中两段。
求能够将数组\(A\)从小到大排序的不一样的操做序列有多少个。
两个操做序列不一样,当且仅当操做个数不一样,或者至少一个操做不一样(种类不一样或者操做位置不一样)。
\(n\le 12\)
解:
首先,任意一个合法的操做序列,咱们能够改变其顺序,依然知足条件。
那么这一类的操做序列的贡献,即为操做次数的阶乘。
那么,如今只考虑种类编号递增的操做序列。第\(i\)种操做时,序列分红了大小为\(2^{i-1}\)的段,若是某个段不是递增且连续的,那么最后确定不会知足条件。因此,在这种操做考虑完后,每一个大小为\(2^i\)的段应当递增且连续。
当考虑第\(i\)种操做时:
农夫约翰须要一些特定规格的木材(共\(n\)块,长度不必定相同),但是他只剩下一些大规格的木板(共\(m\)块,长度不必定相同)。不过约翰能够将这些木板切割成他所须要的规格。
求约翰最多可以获得多少他所须要的木材。
\(n\le 1000,m\le 50,lenth\le 32767\)
解:
显然能够二分答案。
判断答案为\(k\)是否可行,显然能够将木材排序,搜索最短的k 块木材是否
能获得。
而后剪枝:
给出一个数字\(S\),输出全部约数和等于\(S\)的数
一共\(T\)次询问
\(S\le 2e9,T\le 100\)
解:
\(n=\prod_i p_i^{a^i}\Rightarrow \sigma(n)=\prod_i\sum_{j=0}^{a_i}p_i^j\)
那么咱们能够经过枚举\(p_i\)及其\(a_i\)来搜索
若当前须要获得的\(S\)能够表示为为一个未搜索过的质数与\(1\)的和,那么以前的数与这个质数的乘积是一个合法答案
对于每一个使得\((p+1)(p-1)\lt S\)的\(p\),枚举可能的\(a_i\)进行递归
一个\(n\times m\)的网格,\(k\)种颜色,部分格子已经涂了某种颜色,如今须要将其余格子也涂上颜色,使得从\((1,1)\)到\((n,m)\)的每条路径(每次向下或向右走一格)都不会出现重复颜色。求方案数,对\(10^9+7\)取模。
\(n,m\le 1000,k\le 10\)
解:
颜色数应大于等于步数,\(n+m-1\le k\Rightarrow n+m\le k+1\le 11\),不然puts("0")。
而后搜每一个位置的颜色,能够状压到每一个位置的已通过的颜色。
可行性剪枝:未通过的颜色数小于剩余步数,剪掉。
对称性剪枝:若是涂上一个未出现过的颜色,涂哪个都是等价的,那么只需搜其中一个。
有\(52\)张牌排成一排\(\{0,1\cdots 51\}\),而后能够把他们分红两半,而后交叉着洗牌(一个肯定的置换)。
洗完牌后,可能会出现一个错误,将一对相邻的牌调换了位置,可是每次洗牌最多只会犯一个错误。
如今给出牌最终的顺序,求最少洗了多少次(保证不超过\(10\)),最少犯几回错误。
解:
一个性质:肯定了洗牌次数后,若真实的最终排列与不犯错误的最终排列,有\(k\)个位置不一样,那么犯错次数\(\lceil\frac{k}{2}\rceil\)。
以此来当作剪枝,枚举洗牌次数和犯错次数,跑\(idA^*\)
你记录了\([0,59]\)这个时间段内到站的全部公交车(数量\(\le 300\)) 的时间,每辆车属于一条线路。
求最少可能有多少条线路
解:
一条线路,能够经过第一辆、第二辆车来肯定。
咱们能够按照到站顺序来肯定每辆车的归属状况。
若是它是线路第一辆车,那么先把他作一个标记。(同时,它的时间应在\([0,29]\))
若是是第二辆,枚举标记过的车辆来肯定一条线路,若是可行,删除该线
路的全部车。
这样,随着搜索的深度增长,可选车辆快速减小,搜索规模大量下降。
同时能够加入若干合法性或者最优性剪枝。好比:做为第一辆的车多于未肯定的车。
给定\(n\)个字符集为\(\{a,b,c,\cdots,m\}\)的字符串\(S_i\),求一种\(\{a,b,c,\cdots,m\}\)到\(\{0,1,2,3,4,5,6,7,8,9,+,\times,=\}\)的映射\(f\),使得全部\(f(S_i)\)均是表达式合法、且成立的等式。
\(n\le 1000,5\le |S_i|\le 11\)
解:
先对约束条件较高的\(+,\times ,=\)进行搜索。 而后,对于每个等式,咱们获得了每一个数的位数,计算等号两侧的值域, 无交集就能够剪枝。随着对应关系的肯定,值域也会愈来愈小。 能够选择从低位开始搜,肯定了低位的值,不相等能够剪枝