Day1.
当时的zxn很弱,弱到连dfs都调不明白就开始去NOIP。
现在他会了dfs,二分答案,求LCA,bfs,拓扑排序。
所以他回去填NOIP2015的题解坑。
T1.我现在依然不知道除了这种尾递归式的写法之外还有啥别的写法……
伪代码:
void dfs(int i,int j,int x)
{
if(满足条件)w[i][j] = x;
dfs(i',j',x + 1);
}
咳我好像现在明白了……
大概一个循环确实能搞出来……
T2.
求最小环,当时zxn心里确实也是这么想的。
由于他太弱了不会dfs,所以他并不知道怎么写。
题解:
dfs一遍,记一下时间戳。
void dfs(int x)
{
vis[x] = 1;
tid[x] = ++ tim;
RepG(i,x)
if(!vis[v])dfs(v);
else ans = min(ans,tid[x] - tid[v] + 1);
//大概是这样的吧
}
然后后来仔细一想,嗯我还是bfs吧。
次奥……?T掉了QAQ
嗯我们还是冷静一下,发现……
如果是bfs,需要拓扑排序,删掉没有用的那些点,只剩下环即可。
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define RepG(i,x) for(int i = head[x] ;~ i ; i = edge[i].next)
#define Rep_d(i,n) for(int i = n ; i > 0 ; i --)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define v edge[i].to
using namespace std;
int read(){
char ch = getchar();
while(ch < '0' || ch > '9')ch = getchar ();
int x = 0;
while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
return x;
}
int to[200005];
bool vis[200005],vis_now[200005];
int q[200005],tid[200005],tim = 0,ans = 1 << 30,end[200005];
int main()
{
int n = read();
Rep(i,n)
{
int a = read();
to[i] = a;
end[to[i]] ++;
}
int h = 0,t = -1;
Rep(i,n)
if(!end[i])vis[q[++ t] = i] = 1;
while(h <= t){
int x = q[h ++];
end[to[x]] --;
if(!end[to[x]])vis[q[++ t] = to[x]] = 1;
}
Rep(i,n){
if(!vis[i]){
h = 0,t = -1;
vis[i] = 1;
q[++ t] = i;
tim = 0;
tid[i] = 0;
while(h <= t){
int x = q[h ++];
if(!vis_now[to[x]])vis_now[q[++ t] = to[x]] = 1,tid[to[x]] = ++ tim,vis[to[x]] = 1;
else ans = min(ans,tim - tid[to[x]] + 1),h = t + 1;
}
}
}
printf("%d\n",ans);
return 0;
}
QAQ当时明明知道标算然而就是写不出来?
T3.
斗地主。
张地主出的一道水题。张地主亲口说道:“NOIP的一道水题。”
张地主这次CTSC还出了一道提交答案题。
“我们可以发现第8个点是个网格图。”
“第九个点是除了前几个调换了下顺序之外的网格图。”
“第十个点是挖掉了一些点的网格图。”
whx:“我要吐槽!怎么检验它是网格图呢?”
“我的暴力spfa怎么跑的这么慢呢……?”
TAT
题解:
暴力搜索即可。
我们考虑对于当前的手牌,先枚举顺子应该会优一点,因为这样方便加最优性剪枝。
我们考虑:
if(ans <= depth )return;
加这个剪枝就过了。
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define RepG(i,x) for(int i = head[x] ;~ i ; i = edge[i].next)
#define Rep_d(i,n) for(int i = n ; i > 0 ; i --)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define v edge[i].to
using namespace std;
int s[20];
int read(){
char ch = getchar();
while(ch < '0' || ch > '9')ch = getchar ();
int x = 0;
while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
return x;
}
int Cases,n,ans;
void dfs(int now){
if(now >= ans)return;
int a = 0,b = 0,c = 0;
Rep_0(i,14)if(s[i] == 1)a ++;
Rep_0(i,13)if(s[i] == 2)b ++;
Rep_0(i,13)
if(s[i] == 4){
c ++;
if(a >= 2)a -= 2;
else if(b >= 2)b -= 2;
else if(b >= 1)b --;
}
Rep_0(i,13)
if(s[i] == 3){
c ++;
if(a >= 1)a --;
else if(b >= 1)b --;
}
ans = min(ans,a + b + c + now);
Rep_0(i,8){
int j;
for(j = i ; j <= 11 ; j ++){
s[j] --;
if(s[j] < 0)break;
if(j - i >= 4)dfs(now + 1);
}
if(j == 12)j --;
while(j >= i)s[j --] ++;
}
Rep_0(i,10){
int j;
for(j = i; j <= 11; j ++){
s[j] -= 2;
if(s[j] < 0)break;
if(j - i >= 2)dfs(now + 1);
}
if(j == 12)j --;
while(j >= i)s[j --] += 2;
}
Rep_0(i,11){
int j;
for(j = i; j <= 11; j ++){
s[j] -= 3;
if(s[j] < 0)break;
if(j - i > 0)dfs(now + 1);
}
if(j == 12)j --;
while(j >= i)s[j --] += 3;
}
}
int main()
{
Cases = read(),n = read();
while(Cases --){
CLR(s,0);
ans = 10005;
Rep(i,n){
int c = read(),col = read();
if(c < 3 && c)s[10 + c] ++;
else if(c >= 3)s[c - 3] ++ ;
else s[13] ++;
}
dfs(0);
if(s[13] == 2)ans ++;
printf("%d\n",ans);
}
return 0;
}
当时NOIP的时候,zxn表示自己:
“这个可能是某种神奇的搜索,估计写不出来。”
mdzz。
Day2.
zxn:”day1好像挺水的,虽然我不会做,但是算法还是都看出来的。day2应该很友善.”
T1.
跳石头。
zxn:”什么叫二分答案???”
题解:我们对最终那个” 最小的距离最大” 进行二分答案。
也就是说,我们考虑二分那个值,判断是否可行。
现在问题在于怎么O(n)判断可行。
考虑现在有两块石头i和j(i < j )它们连在一起,现在它们的距离小于二分的答案,我们现在想一下该搬哪块……
嗯……
显然是搬走j更优。
我们现在需要考虑的仅仅是这个答案是否可行,所以我们现在面临的条件就是是否能让它花费的石头最少。
考虑这样:
i和j的距离小于二分的距离,并且j和j + 1的距离也小于二分距离。
我们肯定是要继续往后走的。
我们删去i的话以后影响的距离并不受这个i石头的控制,但是我们删去j了之后,不仅消除了前面的i - > j的不合法,而且还有可能让后面的变得合法。
所以删去石头j,即当前扫到的这个石头更优。
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define v edge[i].to
using namespace std;
int read(){
char ch = getchar();
while(ch < '0' || ch > '9')ch = getchar ();
int x = 0;
while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
return x;
}
int n,m,L;
int dis[50005];
bool check(int x){
int p = 0 ,k = 0;
Rep(i,n + 1){
if(dis[i] - dis[k] < x)p ++;
else k = i;
if(p > m)return 0;
}
return 1;
}
int Bin_ans(int l,int r){
if(l == r)return l;
int mid = l + r + 1 >> 1;
if(check(mid))return Bin_ans(mid,r);
return Bin_ans(l,mid - 1);
}
int main()
{
L = read(),n = read(),m = read();
Rep(i,n)
dis[i] = read();
dis[0] = 0;
dis[n + 1] = L;
printf("%d\n",Bin_ans(1,L));
return 0;
}
T2.
zxn在一个月前不可置信地问fsf:”这怎么可能是一道特别简单的DP?”
现在他看到这道题:”哦我自己真是智障。”
设f[i][j][k]表示A串到i,B串到j,一共搞了k个串连起来的方案数。
前缀和 + 滚动数组优化。
这就没了啊QAQ
T3.
zxn原来一直读错题了。
他读成了使得所有的花费总和最小。
题解:使得最大的花费最小,显然二分答案。
问题是怎么check。
我们可以利用它的LCA。
我们考虑这两个点(u,v),如果dis(u,v) >ans,那是要担责任的。
我们把u - > v的路径都标一遍,表示它们用过一遍,并且我们把距离答案最大的差记为lim。
假设我们有p条边要担责任。
当有一条边a它的边权 >= lim,且有use[a] == p,那么我们就可以删掉它了。
总的复杂度我一开始以为这是个暴力,所以一直觉得自己不会做。
现在想想每次复杂度是可以优化成O(n)的啊QAQ
考虑我们每次只标一下那两个节点和它们的LCA。
我们直接对它们到LCA的路径都加一下就好了,但是如果每一个都单独加的话显然是不优的。
我们考虑其实这个玩意可以用dfs序优化一发,就是我们每次找叶子节点进行往上的递推。
这样就是O(n)的辣QAQ
总复杂度是O( nlog2n )
所以zxn还是太弱了QAQ