某大学每一年都会有一次 Mystery Hunt 的活动,玩家须要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍能够得到这一年出题的机会。ios
做为新生的你对这个活动很是感兴趣。你天天都要从西向东通过教学楼一条很长的走廊,这条走廊是如此的长,以致于它被人戏称为 infinite corridor。一次,你通过这条走廊的时,注意到在走廊的墙壁上隐藏着 \(n\) 个等长的二进制的数字,长度均为 \(m\)。你从西向东将这些数字记录了下来,造成一个含有 \(n\) 个数的二进制数组 \(a_1, a_2, ..., a_n\)。很快,在最新的一期 Voo Doo 杂志上,你发现了 \(q\) 个长度也为 \(m\) 的二进制串 \(r_1, r_2, ..., r_q\)。聪明的你很快发现了这些数字的含义。保持数组 \(a_1, a_2, ..., a_n\) 的元素顺序不变,你能够在它们之间插入 \(\wedge\)(按位与运算)或者 \(\vee\)(按位或运算)两种二进制运算符。例如:\(11011 \wedge 00111=00011,11011 \vee 00111=11111\)。数组
你须要插入刚好 \(n\) 个运算符,相邻两个数之间刚好一个,在第一个数的左边还有一个。若是咱们在第一个运算符的左边补入一个 \(0\),这就造成了一个运算式,咱们能够计算它的值。与往常同样,运算顺序是从左往右。有趣的是,出题人已经告诉你这个值的可能的集合——Voo Doo 杂志里的那一些二进制数 \(r_1, r_2, ..., r_q\),而解谜的方法,就是对 \(r_1, r_2, ..., r_q\) 中的每个值 \(r_i\),分别计算出有多少种方法填入这 \(n\) 个运算符,使得这个运算式的值是 \(r_i\) 。然而,infinite corridor 真的很长,这意味着数据范围可能很是大。所以,答案也可能很是大,可是你发现因为谜题的特殊性,你只须要求答案模 \(1000000007\)(\(10^9 + 7\),一个质数)的值。网络
对于 \(10\%\) 的数据,\(n \le 20, m \le 30\),\(q = 1\)数据结构
对于另外 \(20\%\) 的数据,\(n \le 1000\),\(m \le 16\)测试
对于另外 \(40\%\) 的数据,\(n \le 500\),\(m \le 1000\)优化
对于 \(100\%\) 的数据,\(1 \le n \le 1000\),\(1 \le m \le 5000\),\(1 \le q \le 1000\)ui
orz myy. 神题。spa
发现\(|1\)和\(\& 0\)后的结果是必定的,因此某一位最后为1,则要求最后一个&0的位置要在|1以前。
听说这样从后往前爆搜,及时break能够获得70分?!code
而后考虑把操做序列量化成01串,&=1,|=0,则对于某一位来讲,从后往前,当操做串的字典序小于运算元素的串,则最后运算结果为1。排序
这样就很好处理了,把这m个串抠出来,操做串要小于其中一些字串的字典序,大于等于另外一些的。也就是\(x\le op<y\),把\(x,y\)转成二进制数后算差就是op的数量了。对这些串排序后算相邻两数差,最后要么没有答案要么是某相邻两数之差。
注意考场没有开O2因此最好用Trie树排序或者鸡排。
复杂度\(\mathcal O(nm)\)。
#include<iostream> #include<cstdio> #include<cstdlib> #include<vector> #define pb push_back using namespace std; const int N=1100,M=5100,Tr=5e6+10,mod=1e9+7; int n,m,q,nod=1,ch[Tr][2],rk[M],tt,bit[N],d[M]; char s[N][M]; vector<int> tag[Tr]; struct Num { int s[N],rk; int operator - (Num A) const { int res=0; for(int i=n;i>=1;i--) if(s[i]!=A.s[i]) (res+=1ll*(s[i]-A.s[i]+mod)*bit[n-i+1]%mod)%=mod; return res; } }A[M]; void Insert(Num A,int id) { int x=1; for(int i=1;i<=n;i++) { int &v=ch[x][A.s[i]]; if(!v) v=++nod;x=v; } tag[x].pb(id); } void dfs(int x) { for(int l=tag[x].size(),i=0;i<l;i++) rk[++tt]=tag[x][i]; if(ch[x][0]) dfs(ch[x][0]); if(ch[x][1]) dfs(ch[x][1]); } int main() { cin>>n>>m>>q;bit[1]=1; for(int i=2;i<=n;i++) bit[i]=2ll*bit[i-1]%mod; for(int i=1;i<=n;i++) scanf("%s",s[i]+1); for(int i=1;i<=m;i++) for(int j=n,p=0;j>=1;j--) A[i].s[++p]=s[j][i]-'0'; for(int i=1;i<=m;i++) Insert(A[i],i); dfs(1); for(int i=1;i<=m;i++) A[rk[i]].rk=i; for(int i=1;i<m;i++) d[i]=A[rk[i+1]]-A[rk[i]]; for(int i=1;i<=n;i++) A[m+1].s[i]=0,A[m+2].s[i]=1; d[0]=A[rk[1]]-A[m+1],d[m]=(A[m+2]-A[rk[m]]+1)%mod; for(int w=1;w<=q;w++) { scanf("%s",s[0]+1); int ans=0,mxl=0,mnr=m+1; for(int i=1;i<=m;i++) if(s[0][i]=='1') mnr=min(mnr,A[i].rk); else mxl=max(mxl,A[i].rk); if(mxl<mnr) ans=d[mxl]; printf("%d\n",ans); } return 0; }
一次小 G 和小 H 本来准备去聚餐,但因为太麻烦了因而题面简化以下:
一个转盘上有摆成一圈的 \(n\) 个物品(编号 \(1\) 至 \(n\))其中第 \(i\) 个物品会在 \(T_i\) 时刻出现。
在 \(0\) 时刻时,小 G 能够任选 \(n\) 个物品中的一个,咱们将其编号记为 \(s_0\)。而且若是 \(i\) 时刻选择了物品 \(s_i\),那么 \(i + 1\) 时刻能够继续选择当前物品或者选择下一个物品。当 \(s_i\) 为 \(n\) 时,下一个物品为物品 \(1\),不然下一个物品为 \(s_{i} + 1\)。在每一时刻(包括 \(0\) 时刻),若是小 G 所选择的物品已经出现了,那么小 G 将会标记它。小 H 想知道,在物品选择的最优策略下,小 G 何时能标记全部物品?
但麻烦的是,物品的出现时间会不时修改。咱们将其描述为 \(m\) 次修改,每次修改将改变其中一个物品的出现时间。每次修改以后,你也须要求出当前局面的答案。对于其中部分测试点,小 H 还追加了强制在线的要求。
测试点编号 | \(n\) | \(m\) | \(T_i/T_x\) | \(p\) |
---|---|---|---|---|
1 | \(\le 10\) | \(\le 10\) | \(\le 10\) | \(=0\) |
2 | \(\le 1000\) | \(=0\) | \(\le 1000\) | \(=0\) |
3 | \(\le 10^5\) | \(=0\) | \(\le 10^5\) | \(=0\) |
4 | \(\le 5000\) | \(\le 5000\) | \(\le 10^5\) | \(=0\) |
5 | \(\le 8\times 10^4\) | \(\le 8\times 10^4\) | \(\le 10^5\) | \(=0\) |
6 | \(\le 8\times 10^4\) | \(\le 8\times 10^4\) | \(\le 10^5\) | \(=1\) |
7 | \(\le 9\times 10^4\) | \(\le 9\times 10^4\) | \(\le 10^5\) | \(=0\) |
8 | \(\le 9\times 10^4\) | \(\le 9\times 10^4\) | \(\le 10^5\) | \(=1\) |
9 | \(\le 10^5\) | \(\le 10^5\) | \(\le 10^5\) | \(=0\) |
10 | \(\le 10^5\) | \(\le 10^5\) | \(\le 10^5\) | \(=1\) |
我真佩服去年的本身、居然有40分,而今天看了很久才看懂去年的作法。
首先能够证实的是必定是只走一圈。
去年的40分作法:
序列倍长后,\(a[i]=T[i]-i\),对于a维护单调递减队列,答案为n个滑动窗口的队头+i。能够把a当作是等待时间,最后加上n-1就是真正的答案了。
AC作法:
其实答案求的就是\[min_{i=1}^{n}[max_{j=i}^{i+n}A_j+i]\]。
发现\(A_j>A_{j+n}\)后,式子里的max就能够换成后缀max了。考虑用线段树维护这个东西。
每一个节点\((l,r)\)维护\(mx[x]\)表示最大的A,\(ans[x]\)表示\(i\)取到\([l,mid]\)时候的最小答案。
合并信息就从新递归一下,和男神那题超级像。
复杂度\(\mathcal O(nlog^2n)\)。
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; const int N=2e5+10; int n,m,op,T[N],a[N],ans[N<<2],mx[N<<2],Ans; int calc(int x,int l,int r,int b) { if(l==r) return l+max(mx[x],b); int mid=(l+r)>>1; if(mx[x<<1|1]>=b) return min(ans[x],calc(x<<1|1,mid+1,r,b)); else return min(calc(x<<1,l,mid,b),mid+1+b); } void pushup(int x,int l,int r) { mx[x]=max(mx[x<<1],mx[x<<1|1]); ans[x]=calc(x<<1,l,(l+r)>>1,mx[x<<1|1]); } void build(int x,int l,int r) { if(l==r) {mx[x]=a[l],ans[x]=T[l];return;} int mid=(l+r)>>1; build(x<<1,l,mid); build(x<<1|1,mid+1,r); pushup(x,l,r); } void update(int x,int l,int r,int p) { if(l==r) {mx[x]=a[l],ans[x]=T[l];return;} int mid=(l+r)>>1; if(p<=mid) update(x<<1,l,mid,p); else update(x<<1|1,mid+1,r,p); pushup(x,l,r); } int main() { cin>>n>>m>>op; for(int i=1;i<=n;i++) { scanf("%d",&T[i]);a[i]=T[i]-i; T[i+n]=T[i],a[i+n]=T[i]-(i+n); } build(1,1,n*2);printf("%d\n",Ans=ans[1]+n-1); for(int i=1,x,y;i<=m;i++) { scanf("%d%d",&x,&y); if(op) x^=Ans,y^=Ans; T[x]=T[x+n]=y,a[x]=y-x,a[x+n]=y-x-n; update(1,1,n*2,x),update(1,1,n*2,x+n); printf("%d\n",Ans=ans[1]+n-1); } return 0; }
从前有一名毒瘤。
毒瘤最近发现了量产毒瘤题的奥秘。考虑以下类型的数据结构题:给出一个数组,要求支持若干种奇奇怪怪的修改操做(例如给一个区间内的数同时加上 \(c\),或者将一个区间内的数同时开平方根),而且支持询问区间的和。毒瘤考虑了 \(n\) 个这样的修改操做,并将它们编号为 \(1 \ldots n\)。当毒瘤要出数据结构题的时候,他就将这些修改操做中选若干个出来,而后出成一道题。
固然了,这样出的题有可能不可作。经过精妙的数学推理,毒瘤揭露了这些修改操做之间的关系:有 \(m\) 对「互相排斥」的修改操做,第 \(i\) 对是第 \(u_i\) 个操做和第 \(v_i\) 个操做。当一道题中同时含有 \(u_i\) 和 \(v_i\) 这两个操做时,这道题就会变得不可作。另外一方面,当一道题中不包含任何「互相排斥」的操做时,这个题就是可作的。此外,毒瘤还发现了一个规律:\(m − n\) 是一个很小的数字(参见「数据范围」中的说明),且任意两个修改操做都是连通的。两个修改操做 \(a, b\) 是连通的,当且仅当存在若干操做 \(t_0, t_1, ... , t_l\),使得 \(t_0 = a,t_l = b\),且对任意 \(1 \le i \le l\),\(t_{i−1}\) 和 \(t_i\) 都是「互相排斥」的修改操做。
一对「互相排斥」的修改操做称为互斥对。如今毒瘤想知道,给定值 \(n\) 和 \(m\) 个互斥对,他一共能出出多少道可作的不一样的数据结构题。两个数据结构题是不一样的,当且仅当其中某个操做出如今了其中一个题中,可是没有出如今另外一个题中。
测试点 # | 1~4 | 5~6 | 7~8 | 9 | 10~11 | 12~14 | 15~16 | 17~20 |
---|---|---|---|---|---|---|---|---|
\(n \le\) | \(20\) | \(10^5\) | \(10^5\) | \(3000\) | \(10^5\) | \(3000\) | \(10^5\) | \(10^5\) |
\(m \le\) | \(n + 10\) | \(n - 1\) | \(n\) | \(n + 1\) | \(n + 1\) | \(n + 10\) | \(n + 7\) | \(n + 10\) |
就是求有11条返祖边的树的独立集个数。
很良心地给了75左右的暴力容斥部分分。
正解:
把11*2个点抠出来建虚树,一共不到50个点。预处理出没有返祖边的树的dp值。
如今考虑仍然暴力容斥,可是计算过程能够只用在虚树上计算,也就是说优化掉一个\(n\)。
发现虚树上每条边的转移系数是必定的,把未知数代进去转移、就能够预处理出转移系数了。
具体来讲个人\(g[x][0/1]=(a,b)\),\(x\)的虚树父亲为\(f\),则
\[ dp[f][0]*=(g[x][0].a*dp[x][0]+g[x][1].a*dp[x][1]); \]
\[ dp[f][1]*=(g[x][0].b*dp[x][0]+g[x][1].b*dp[x][1]); \]
因此显然初值\(g[x][0]=(1,1),g[x][1]=(1,0)\) 。
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<vector> #define pa pair<int,int> #define fi first #define se second #define mp make_pair #define pb push_back using namespace std; const int N=2e5+10,mod=998244353; struct edge{int next,to;}a[N]; int head[N],cnt,n,m,sf[N],fa[N],f[N][2],ST[N][18]; int dfn[N],S[N],c,tot,dep[N],sta[N],top,out[N]; void link(int x,int y) {a[++cnt]=(edge){head[x],y};head[x]=cnt;} int find(int x) {return sf[x]==x?x:sf[x]=find(sf[x]);} int ksm(int x,int k) { int s=1;for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) s=1ll*s*x%mod;return s; } void dfs(int x,int fr) { fa[x]=fr;ST[x][0]=fr; dep[x]=dep[fr]+1;dfn[x]=++tot; for(int p=1;p<=16;p++) ST[x][p]=ST[ST[x][p-1]][p-1]; f[x][0]=f[x][1]=1; for(int i=head[x];i;i=a[i].next) { int R=a[i].to;if(R==fr) continue; dfs(R,x); f[x][0]=1ll*f[x][0]*(f[R][0]+f[R][1])%mod; f[x][1]=1ll*f[x][1]*f[R][0]%mod; } out[x]=tot; } int LCA(int x,int y) { if(dep[x]<dep[y]) swap(x,y); for(int p=16;p>=0;p--) if(dep[ST[x][p]]>=dep[y]) x=ST[x][p]; for(int p=16;p>=0;p--) if(ST[x][p]!=ST[y][p]) x=ST[x][p],y=ST[y][p]; return x==y?x:ST[x][0]; } void Jump(int &R,int x) { for(int p=16;p>=0;p--) if(dep[ST[R][p]]>dep[x]) R=ST[R][p]; } int cmp(int a,int b) {return dfn[a]<dfn[b];} vector<int> E[N]; int ban[N],g[N][2],s,Ans; pa f0[N],f1[N],M[N]; void Calc(int x,int y) { int p=x; f0[x]=mp(1,1);f1[x]=mp(1,0); while(fa[p]!=y) { int bs0=1,bs1=1; for(int i=head[fa[p]];i;i=a[i].next) { int R=a[i].to; if(R==fa[fa[p]]||R==p) continue; bs0=1ll*bs0*(f[R][0]+f[R][1])%mod; bs1=1ll*bs1*f[R][0]%mod; } pa ff0=mp(1ll*f0[x].fi*bs0%mod,1ll*f0[x].se*bs0%mod); pa ff1=mp(1ll*f1[x].fi*bs1%mod,1ll*f1[x].se*bs1%mod); f0[x]=mp((ff0.fi+ff1.fi)%mod,(ff0.se+ff1.se)%mod); f1[x]=mp(ff0.fi,ff0.se); p=fa[p]; } } int DP() { for(int i=1;i<=c;i++) g[S[i]][1]=1,g[S[i]][0]=ban[S[i]]?0:1; for(int i=c;i>=1;i--) { int x=S[i]; g[x][0]*=f[x][0],g[x][1]*=f[x][1]; for(int j=0,l=E[x].size();j<l;j++) { int R=E[x][j];Jump(R,x); g[x][0]=1ll*g[x][0]*ksm((f[R][0]+f[R][1])%mod,mod-2)%mod; g[x][1]=1ll*g[x][1]*ksm(f[R][0],mod-2)%mod; } for(int j=0,l=E[x].size();j<l;j++) { int R=E[x][j]; g[x][0]=1ll*g[x][0]*(1ll*f0[R].fi*g[R][0]%mod+1ll*f0[R].se*g[R][1]%mod)%mod; g[x][1]=1ll*g[x][1]*(1ll*f1[R].fi*g[R][0]%mod+1ll*f1[R].se*g[R][1]%mod)%mod; } } return (g[1][0]+g[1][1])%mod; } int main() { cin>>n>>m; for(int i=1;i<=n;i++) sf[i]=i; for(int i=1,x,y;i<=m;i++) { scanf("%d%d",&x,&y); if(find(x)!=find(y)) sf[find(x)]=find(y),link(x,y),link(y,x); else M[++s]=mp(x,y),S[++c]=x,S[++c]=y; } dfs(1,0); sort(S+1,S+c+1,cmp); for(int i=2,t=c;i<=t;i++) S[++c]=LCA(S[i-1],S[i]); S[++c]=1;sort(S+1,S+c+1,cmp); c=unique(S+1,S+c+1)-S-1; for(int i=1;i<=c;sta[++top]=S[i],i++) { while(top&&dfn[S[i]]>out[sta[top]]) top--; if(top) E[sta[top]].pb(S[i]),Calc(S[i],sta[top]); } for(int zt=0,d=1;zt<1<<s;zt++) { for(int i=1;i<=s;i++) if(zt&(1<<(i-1))) d=mod-d,ban[M[i].fi]=ban[M[i].se]=1; int res=DP(); (Ans+=1ll*res*d%mod)%=mod; d=1;for(int i=1;i<=s;i++) ban[M[i].fi]=ban[M[i].se]=0; } cout<<Ans<<endl; }
一次小 G 和小 H 在玩寻宝游戏,有 \(n\) 个房间排成一列,编号为 \(1,2,…,n\),相邻房间之间都有 \(1\) 道门。其中一部分门上有锁(所以须要对应的钥匙才能开门),其他的门都能直接打开。
如今小 G 告诉了小 H 每把锁的钥匙在哪一个房间里(每把锁有且只有一把钥匙),并做出 \(p\) 次指示:第 \(i\) 次让小 H 从第 \(S_i\) 个房间出发,去第 \(T_i\) 个房间寻宝。可是小 G 有时会故意在指令里放入死路,而小 H 也不想浪费多余的体力去尝试,因而想事先调查清楚每次的指令是否存在一条通路。
你是否能为小 H 做出解答呢?
测试点编号 | n | m | 其余特性 |
---|---|---|---|
1 | $ \le 1000 $ | $ \le 1000 $ | 无 |
2 | $ \le 1000 $ | $ \le 1000 $ | 无 |
3 | $ \le 10^5 $ | $ \le 10^5 $ | \(y \le x\) 恒成立 |
4 | $ \le 10^5 $ | $ \le 10^5 $ | \(y \le x\) 恒成立 |
5 | $ \le 10^5 $ | $ \le 10^5 $ | 无 |
6 | $ \le 10^5 $ | $ \le 10^5 $ | 无 |
7 | $ \le 10^6 $ | $ \le 10^6 $ | \(y \le x\) 恒成立 |
8 | $ \le 10^6 $ | $ \le 10^6 $ | \(y \le x\) 恒成立 |
9 | $ \le 10^6 $ | $ \le 10^6 $ | 无 |
10 | $ \le 10^6 $ | $ \le 10^6 $ | 无 |
对于全部数据,保证 \(1 \le n,p \le 10^6\),\(0 \le m < n\),\(1 \le x, y, S_i,T_i < n\),保证 \(x\) 不重复。
因为本题输入文件较大,建议在程序中使用读入优化。
被暴力艹过90分真的无语。省选若是出现这种状况退役那也没有什么办法了。
方法是对于每一个点维护向左以及向右最多能到的区间。
先用单调栈维护最大可能区间,以后由\([l,r]\)扩展,找到\([LS,l-1]\)中从右往左第一个\(key[i]>r\)的地方,并把\(l\)设置为\(i+1\)。以后即可以拓展右区间。
因为右区间的扩展相似于单调栈,因此不难发现拓展次数最多为\(\mathcal O(n)\)。所以总复杂度为\(\mathcal O(nlogn)\) 。
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; const int N=1e6+10; int n,m,q,key[N],l[N],r[N],t[N<<2]; int sta[N],top,LS[N],RS[N]; void build(int x,int l,int r) { if(l==r) {t[x]=key[l];return;} int mid=(l+r)>>1; build(x<<1,l,mid); build(x<<1|1,mid+1,r); t[x]=max(t[x<<1],t[x<<1|1]); } int query(int x,int l,int r,int gl,int gr,int bs) { int mid=(l+r)>>1,res=0; if(l>=gl&&r<=gr) { if(t[x]<=bs) return 0; if(l==r) return l; if(t[x<<1|1]>bs) return query(x<<1|1,mid+1,r,gl,gr,bs); else return query(x<<1,l,mid,gl,gr,bs); } if(gr>mid) res=query(x<<1|1,mid+1,r,gl,gr,bs); if(gl<=mid&&!res) res=query(x<<1,l,mid,gl,gr,bs); return res; } int Find(int l,int r,int x) { if(l>r) return l; int p=query(1,1,n,l,r,x); return p?p+1:l; } int main() { cin>>n>>m>>q; for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),key[x]=y; build(1,1,n);LS[1]=1,RS[n]=n; for(int i=2;i<=n;i++) LS[i]=(key[i-1]&&key[i-1]<=i-1)?i:LS[i-1]; for(int i=n-1;i>=1;i--) RS[i]=(key[i]&&key[i]>i)?i:RS[i+1]; for(int i=n;i>=1;i--) { r[i]=i;l[i]=Find(LS[i],i-1,i); while(r[i]<n&&(!key[r[i]]||(key[r[i]]>=l[i]&&key[r[i]]<=r[i]))) r[i]=r[r[i]+1],l[i]=Find(LS[i],l[i]-1,r[i]); } for(int i=1,x,y;i<=q;i++) scanf("%d%d",&x,&y),puts((l[x]<=y&&r[x]>=y)?"YES":"NO"); }
给定 \(n\) 个整数 \(a_1, a_2, \ldots , a_n(0 \le a_i \le n)\),以及 \(n\) 个整数 \(w_1, w_2, …, w_n\)。称 \(a_1, a_2, \ldots , a_n\) 的一个排列 \(a_{p[1]}, a_{p[2]}, \ldots , a_{p[n]}\) 为 \(a_1, a_2, \ldots , a_n\) 的一个合法排列,当且仅当该排列知足:对于任意的 \(k\) 和任意的 \(j\),若是 \(j \le k\),那么 \(a_{p[j]}\) 不等于 \(p[k]\)。(换句话说就是:对于任意的 \(k\) 和任意的 \(j\),若是 \(p[k]\) 等于 \(a_{p[j]}\),那么 \(k<j\)。)
定义这个合法排列的权值为 \(w_{p[1]} + 2w_{p[2]} + \ldots + nw_{p[n]}\)。你须要求出在全部合法排列中的最大权值。若是不存在合法排列,输出 \(-1\)。
样例解释中给出了合法排列和非法排列的实例。
对于前 \(20\%\) 的数据,\(1 \le n \le 10\);
对于前 \(40\%\) 的数据,\(1 \le n \le 15\);
对于前 \(60\%\) 的数据,\(1 \le n \le 1000\);
对于前 \(80\%\) 的数据,\(1 \le n \le 100000\);
对于 \(100\%\) 的数据,\(1 \le n \le 500000\),\(0 \le a_i \le n (1 \le i \le n)\),\(1 \le w_i \le 10^9\) ,全部 \(w_i\) 的和不超过 \(1.5 \times 10^{13}\)。
这题的映射关系很是复杂好嘛!但愿不要出现这种题目特别难懂的题目了!
把这题映射关系搞清楚后,发现就是\(a[i]->i\),而后在这棵树(有环无解)上按照拓扑序依次选完全部的点,贡献为选某点的时间×该点权值。
这样大概有40分的状压DP,可是考虑正解:
显然权值小的点要先选,那么权值最小的点在选完其父亲(若是有的话)后,必定立刻被选。
考虑每一个点向父亲缩,代价为父亲的siz×该点的val。因而各个联通块的权值如何肯定呢?
考虑两个联通块AB,当前时刻为i,能够很轻松地列出\(W_{AB},W_{BA}\)的式子,相减发现\(\frac{\sum val}{siz}\)小的被先选会更优。
因此用一个set维护每一个点,每次选取最小点向父亲合并,最后合成一个点就行了。
这题据说是YALI考过的题,HNOI考完走出考场听到许多“YALI人AK了”之类的言语,不是很爽快——HNOI有YALI学长出的题。固然不能否认的是YALI确实很强,应该也没有泄题的状况。可是我总以为在这种大赛搬原题是一种极其不负责任的表现。
#include<iostream> #include<cstdio> #include<cstdlib> using namespace std; const int N=1e6+10; int n,m,q,key[N],l[N],r[N],t[N<<2]; int sta[N],top,LS[N],RS[N]; void build(int x,int l,int r) { if(l==r) {t[x]=key[l];return;} int mid=(l+r)>>1; build(x<<1,l,mid); build(x<<1|1,mid+1,r); t[x]=max(t[x<<1],t[x<<1|1]); } int query(int x,int l,int r,int gl,int gr,int bs) { int mid=(l+r)>>1,res=0; if(l>=gl&&r<=gr) { if(t[x]<=bs) return 0; if(l==r) return l; if(t[x<<1|1]>bs) return query(x<<1|1,mid+1,r,gl,gr,bs); else return query(x<<1,l,mid,gl,gr,bs); } if(gr>mid) res=query(x<<1|1,mid+1,r,gl,gr,bs); if(gl<=mid&&!res) res=query(x<<1,l,mid,gl,gr,bs); return res; } int Find(int l,int r,int x) { if(l>r) return l; int p=query(1,1,n,l,r,x); return p?p+1:l; } int main() { cin>>n>>m>>q; for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),key[x]=y; build(1,1,n);LS[1]=1,RS[n]=n; for(int i=2;i<=n;i++) LS[i]=(key[i-1]&&key[i-1]<=i-1)?i:LS[i-1]; for(int i=n-1;i>=1;i--) RS[i]=(key[i]&&key[i]>i)?i:RS[i+1]; for(int i=n;i>=1;i--) { r[i]=i;l[i]=Find(LS[i],i-1,i); while(r[i]<n&&(!key[r[i]]||(key[r[i]]>=l[i]&&key[r[i]]<=r[i]))) r[i]=r[r[i]+1],l[i]=Find(LS[i],l[i]-1,r[i]); } for(int i=1,x,y;i<=q;i++) scanf("%d%d",&x,&y),puts((l[x]<=y&&r[x]>=y)?"YES":"NO"); }
W 国的交通呈一棵树的形状。W 国一共有 \(n − 1\) 个城市和 \(n\) 个乡村,其中城市从 \(1\) 到 \(n − 1\) 编号,乡村从 \(1\) 到 \(n\) 编号,且 \(1\) 号城市是首都。道路都是单向的,本题中咱们只考虑从乡村通往首都的道路网络。对于每个城市,恰有一条公路和一条铁路通向这座城市。对于城市 \(i\),通向该城市的道路(公路或铁路)的起点,要么是一个乡村,要么是一个编号比 \(i\) 大的城市。没有道路通向任何乡村。除了首都之外,从任何城市或乡村出发只有一条道路;首都没有往外的道路。从任何乡村出发,沿着惟一往外的道路走,总能够到达首都。
W 国的国王小 W 得到了一笔资金,他决定用这笔资金来改善交通。因为资金有限,小 W 只能翻修 \(n − 1\) 条道路。小 W 决定对每一个城市翻修刚好一条通向它的道路,即从公路和铁路中选择一条并进行翻修。小 W 但愿从乡村通向城市能够尽量地便利,因而根据人口调查的数据,小 W 对每一个乡村制定了三个参数,编号为 \(i\) 的乡村的三个参数是 \(a_i\),\(b_i\) 和 \(c_i\)。假设从编号为 \(i\) 的乡村走到首都一共须要通过 \(x\) 条未翻修的公路与 \(y\) 条未翻修的铁路,那么该乡村的不便利值为
\[ c_i \cdot (ai + x) \cdot (bi + y) \]
在给定的翻修方案下,每一个乡村的不便利值相加的和为该翻修方案的不便利值。
翻修 \(n − 1\) 条道路有不少方案,其中不便利值最小的方案称为最优翻修方案,小 W 天然但愿找到最优翻修方案,请你帮助他求出这个最优翻修方案的不便利值。
共 \(20\) 组数据,编号为 \(1 ∼ 20\)。
对于编号 \(\le 4\) 的数据,\(n \le 20\);
对于编号为 \(5 \sim 8\) 的数据,\(a_i, b_i, c_i \le 5,n \le 50\);
对于编号为 \(9 \sim 12\) 的数据,\(n \le 2000\);
对于全部的数据,\(n \le 20000\),\(1 \le a_i, b_i \le 60\),\(1 \le c_i \le 10^9\),\(s_i, t_i\) 是 \([−n, −1] \cap (i, n − 1]\) 内的整数,任意乡村能够经过不超过 \(40\) 条道路到达首都。
听说这题出题人想复杂了因而成为了普及题。。。验题人干吗去了啊。。
然而我刚才苦苦思索十分钟仍是忘记怎么作了(去年作的)。。就怕被降智啊!!!
设\(dp[x][a][b]\)表示\(x\)的子树内,到根还有a条没有修好的公路、b条没有修好的铁路的最小总代价。
没了。
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define ll long long using namespace std; int read() { char ch=getchar();int h=0,t=1; while(ch!='-'&&(ch>'9'||ch<'0'))ch=getchar(); if(ch=='-')t=-1,ch=getchar(); while(ch>='0'&&ch<='9'){h=h*10+ch-'0';ch=getchar();} return h*t; } const int MAXN=40010; int N,head[MAXN],cnt,L[MAXN],R[MAXN]; int A[MAXN],B[MAXN],C[MAXN]; ll dp[MAXN>>1][41][41]; struct edge{int next,to,w;}a[MAXN<<2]; void link(int x,int y,int w){a[++cnt]=(edge){head[x],y,w};head[x]=cnt;} void Pre(int x,int fa) { for(int i=head[x];~i;i=a[i].next) { int S=a[i].to; if(S==fa)continue; L[S]=L[x],R[S]=R[x]; (a[i].w==1)?L[S]++:R[S]++; Pre(S,x); } } ll DP(int x,int i,int j) { if(x<=N) return dp[x][i][j]; return 1LL*C[x]*(A[x]+i)*(B[x]+j); } void DFS(int x,int fa) { int lc=0,rc=0; for(int i=head[x];~i;i=a[i].next) if(a[i].to!=fa){DFS(a[i].to,x);rc?lc=a[i].to:rc=a[i].to;} if(!lc) return; for(int i=0;i<=L[x];i++) for(int j=0;j<=R[x];j++) dp[x][i][j]=min(DP(lc,i+1,j)+DP(rc,i,j),DP(lc,i,j)+DP(rc,i,j+1)); } int main() { N=read(); memset(head,-1,sizeof(head)); for(int i=1;i<N;i++) { int x=read(),y=read(); if(x<0)x=-x+N; if(y<0)y=-y+N; link(i,x,1);link(x,i,1); link(i,y,2);link(y,i,2); } for(int i=1;i<=N;i++) { int pos=i+N; A[pos]=read(); B[pos]=read(); C[pos]=read(); } Pre(1,0); DFS(1,0); printf("%lld\n",dp[1][0][0]); return 0; }
这套题目能够说是很是好、质量很是高的啦。
若是今年让我考这套题目的话,最好的成绩是30+40+75+60+40+100,然而算上联赛也只能踩队线。
能够说很是刺激了。
后天加油啊!